draw.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. package editor
  2. import (
  3. "os"
  4. "fmt"
  5. "math"
  6. "moose/internal/buffer"
  7. "moose/internal/layout"
  8. "moose/internal/util"
  9. "moose/internal/highlight"
  10. "slices"
  11. "strconv"
  12. "strings"
  13. "github.com/davecgh/go-spew/spew"
  14. "github.com/gdamore/tcell/v3"
  15. )
  16. func (m *Model) Draw() {
  17. screen := layout.RectFromScren(m.Screen)
  18. main := layout.Rect{
  19. X: screen.X,
  20. Y: screen.Y,
  21. Width: screen.Width,
  22. Height: screen.Height - 2,
  23. }
  24. palette := layout.Rect{
  25. X: screen.X,
  26. Y: main.Height,
  27. Width: screen.Width,
  28. Height: 2,
  29. }
  30. copy := m.LM.Workspaces[m.LM.ActiveIdx]
  31. m.DrawWorkspace(&copy, main)
  32. m.DrawPalette(palette)
  33. }
  34. func (m *Model) generateChordStr() string {
  35. chordStr := ""
  36. if len(m.AM.RCM.Recorded) > 0 {
  37. chordStr += strings.Join(m.AM.RCM.Recorded, "") + " + "
  38. }
  39. if len(m.AM.CM.Recorded) > 0 {
  40. chordStr += strings.Join(m.AM.CM.Recorded, "+") + " +"
  41. }
  42. return chordStr
  43. }
  44. func (m *Model) DrawPalette(rect layout.Rect) {
  45. for col := 0; col < rect.Width; col++ {
  46. m.Screen.SetContent(rect.X+col, rect.Y, ' ', nil, m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.PaletteBarBackground)).Foreground(tcell.GetColor(m.Config.Colors.PaletteBarForeground)))
  47. }
  48. modeStr := m.Mode.String()
  49. if m.Mode == ModePalette {
  50. if strings.HasPrefix(m.BM.PaletteBuffer.String(), "/") {
  51. modeStr += " (command)"
  52. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "?") {
  53. modeStr += " (find)"
  54. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "=") {
  55. modeStr += " (replace)"
  56. }
  57. }
  58. m.Screen.PutStrStyled(rect.Width-(len(modeStr)+1), rect.Y, strings.ToUpper(modeStr), m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.PaletteBarForeground)).Background(tcell.GetColor(m.Config.Colors.PaletteBarBackground)))
  59. splitStr := ""
  60. if m.LM.CurrentSplit == layout.SplitHorizontal {
  61. splitStr = "H"
  62. } else {
  63. splitStr = "V"
  64. }
  65. m.Screen.PutStrStyled(rect.Width-(len(modeStr)+1)-(m.Config.Properties.GutterWidth+1), rect.Y, splitStr, m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.PaletteBarForeground)).Background(tcell.GetColor(m.Config.Colors.PaletteBarBackground)))
  66. populatedWorkspaces := []int{m.LM.ActiveIdx + 1}
  67. for workspaceIdx, workspace := range m.LM.Workspaces {
  68. if workspaceIdx == m.LM.ActiveIdx {
  69. continue
  70. }
  71. if !workspace.IsEmpty() {
  72. populatedWorkspaces = append(populatedWorkspaces, workspaceIdx+1)
  73. }
  74. }
  75. slices.Sort(populatedWorkspaces)
  76. strWorkspaces := make([]string, len(populatedWorkspaces))
  77. for i, v := range populatedWorkspaces {
  78. strWorkspaces[i] = strconv.Itoa(v)
  79. }
  80. workspacesStr := strings.Join(strWorkspaces, " ")
  81. m.Screen.PutStrStyled(1, rect.Y, workspacesStr, m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.WorkspaceForeground)).Background(tcell.GetColor(m.Config.Colors.WorkspaceBackground)))
  82. for idx, workspace := range populatedWorkspaces {
  83. if workspace == m.LM.ActiveIdx+1 {
  84. m.Screen.SetContent(idx*2+1, rect.Y, '0'+rune(m.LM.ActiveIdx+1), nil, m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.WorkspaceBackgroundActive)).Foreground(tcell.GetColor(m.Config.Colors.WorkspaceForegroundActive)))
  85. }
  86. }
  87. chordStr := m.generateChordStr()
  88. m.Screen.PutStrStyled(len(workspacesStr)+2, rect.Y, chordStr, m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.PaletteBarForeground)).Background(tcell.GetColor(m.Config.Colors.PaletteBarBackground)))
  89. m.Screen.PutStrStyled(len(workspacesStr)+2+len(chordStr)+1, rect.Y, m.DebugLog, m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.PaletteBarForeground)).Background(tcell.GetColor(m.Config.Colors.PaletteBarBackground)))
  90. if m.Mode == ModePalette {
  91. m.Screen.PutStrStyled(0, rect.Y+1, m.BM.PaletteBuffer.String(), m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.PaletteInputForeground)).Background(tcell.GetColor(m.Config.Colors.PaletteInputBackground)))
  92. for _, cur := range m.BM.PaletteBuffer.CM.Cursors {
  93. line, col := buffer.LineCol(&m.BM.PaletteBuffer, cur.Offset)
  94. if line+rect.Y != rect.Y {
  95. continue
  96. }
  97. if col+1 > rect.Width {
  98. continue
  99. }
  100. ch := buffer.RuneAt(&m.BM.PaletteBuffer, cur.Offset)
  101. m.Screen.SetContent(col, rect.Y+1, ch, nil, m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.CursorColorWrite)))
  102. }
  103. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "moose.info:") {
  104. m.Screen.PutStrStyled(0, rect.Y+1, string([]rune(m.BM.PaletteBuffer.String())[11:]), m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.InfoMsgForeground)).Background(tcell.GetColor(m.Config.Colors.InfoMsgBackground)))
  105. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "moose.warn:") {
  106. m.Screen.PutStrStyled(0, rect.Y+1, string([]rune(m.BM.PaletteBuffer.String())[11:]), m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.WarnMsgForeground)).Background(tcell.GetColor(m.Config.Colors.WarnMsgBackground)))
  107. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "moose.error:") {
  108. m.Screen.PutStrStyled(0, rect.Y+1, string([]rune(m.BM.PaletteBuffer.String())[12:]), m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.ErrorMsgForeground)).Background(tcell.GetColor(m.Config.Colors.ErrorMsgBackground)))
  109. }
  110. }
  111. func (m *Model) DrawWorkspace(w *layout.Workspace, rect layout.Rect) {
  112. m.DrawContainer(&w.RootContainer, rect)
  113. }
  114. func (m *Model) DrawContainer(c *layout.Container, rect layout.Rect) {
  115. length := util.NonNilLen(c.Children[:])
  116. if length == 0 {
  117. return
  118. }
  119. rect = layout.Rect{
  120. X: rect.X,
  121. Y: rect.Y,
  122. Width: rect.Width,
  123. Height: rect.Height,
  124. }
  125. rect = layout.RectDivide(rect, c.Split, length)
  126. for i, child := range c.Children {
  127. main := layout.RectDisplace(rect, c.Split, i)
  128. switch child.(type) {
  129. case layout.ContainerBuffers:
  130. {
  131. cb := child.(layout.ContainerBuffers)
  132. tabs := layout.Rect{
  133. X: main.X,
  134. Y: main.Y,
  135. Width: main.Width,
  136. Height: 1,
  137. }
  138. main.Y += 1
  139. main.Height -= 1
  140. m.DrawContainerTabs(&cb, tabs)
  141. m.DrawContainerBuffers(&cb, main)
  142. }
  143. case layout.Container:
  144. {
  145. c := child.(layout.Container)
  146. m.DrawContainer(&c, main)
  147. }
  148. }
  149. }
  150. }
  151. func (m *Model) DrawContainerTabs(c *layout.ContainerBuffers, rect layout.Rect) {
  152. tabs := []string{}
  153. activeTabIdx := -1
  154. for _, bufIdx := range c.Buffers {
  155. if bufIdx >= len(m.BM.Buffers) {
  156. continue
  157. }
  158. if c.ActiveIdx < len(c.Buffers) && c.Buffers[c.ActiveIdx] == bufIdx {
  159. activeTabIdx = len(tabs)
  160. }
  161. buf := m.BM.Buffers[bufIdx]
  162. if buf.Path == "" {
  163. tabs = append(tabs, "Buffer " + strconv.Itoa(bufIdx))
  164. } else {
  165. tabs = append(tabs, buf.Path)
  166. }
  167. }
  168. length := 0
  169. for i, tab := range tabs {
  170. if length+len(tab)+2 > rect.Width {
  171. m.Screen.PutStrStyled(rect.X+length, rect.Y, ">", m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.TabForeground)).Background(tcell.GetColor(m.Config.Colors.TabBackground)))
  172. return
  173. }
  174. style := m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.TabBackground)).Foreground(tcell.GetColor(m.Config.Colors.TabForeground))
  175. if i == activeTabIdx {
  176. style = style.Foreground(tcell.GetColor(m.Config.Colors.TabForegroundActive)).Background(tcell.GetColor(m.Config.Colors.TabBackgroundActive))
  177. }
  178. tabStr := strconv.Itoa(int(math.Abs(float64((activeTabIdx - i))))) + ": " + tab
  179. m.Screen.PutStrStyled(rect.X+length, rect.Y, tabStr, style)
  180. length += len(tabStr) + 1
  181. }
  182. }
  183. func (m *Model) DrawContainerBuffers(c *layout.ContainerBuffers, rect layout.Rect) {
  184. if c.ActiveIdx >= len(c.Buffers) {
  185. return
  186. }
  187. if c.Buffers[c.ActiveIdx] >= len(m.BM.Buffers) {
  188. return
  189. }
  190. m.DrawBuffer(&m.BM.Buffers[c.Buffers[c.ActiveIdx]], c.Buffers[c.ActiveIdx] == m.BM.CurrentIdx, rect)
  191. }
  192. const tabWidth = 4
  193. func (m *Model) DrawBuffer(buf *buffer.Buffer, isActive bool, rect layout.Rect) {
  194. primary := buf.CM.Cursors[buf.CM.PrimaryIdx]
  195. curLine, _ := buffer.LineCol(buf, primary.Offset)
  196. buf.ScrollToShow(curLine, rect.Height)
  197. startOffset := buffer.OffsetForLine(buf, buf.TopLine)
  198. endOffset := buf.Rope.Len()
  199. if endLine := buf.TopLine + rect.Height; endLine < buffer.LineCount(buf) {
  200. endOffset = buffer.OffsetForLine(buf, endLine)
  201. }
  202. defaultStyle := m.Config.StyleDefault.
  203. Foreground(tcell.GetColor(m.Config.Colors.MainForeground)).
  204. Background(tcell.GetColor(m.Config.Colors.MainBackground))
  205. if buf.IsDirty && m.Highlighter != nil && buf.Language != "" && len(buf.HighlightQuery) > 0 {
  206. fullCode := []byte(buf.String())
  207. spans, newTree, err := m.Highlighter.HighlightBuffer(buf.Language, fullCode, buf.HighlightQuery)
  208. if err != nil {
  209. os.WriteFile("output.txt", []byte(spew.Sdump(err)), 0644)
  210. }
  211. if err == nil {
  212. if buf.Tree != nil {
  213. buf.Tree.Close()
  214. }
  215. buf.Tree = newTree
  216. buf.Spans = spans
  217. }
  218. // buf.IsDirty = false
  219. }
  220. visible := string(buf.Rope.Slice(startOffset, endOffset))
  221. table := strings.SplitAfter(visible, "\n")
  222. currentLineByteOffset := uint32(startOffset)
  223. // tabWidth := m.Config.Properties.TabWidth
  224. // if tabWidth <= 0 {
  225. // tabWidth = 4
  226. // }
  227. for j, line := range table {
  228. if j+1 > rect.Height {
  229. break
  230. }
  231. lineNum := j + buf.TopLine
  232. relLine := lineNum - curLine
  233. gutterText := fmt.Sprintf("%*d ", m.Config.Properties.GutterWidth, int(math.Abs(float64(relLine))))
  234. gutterStyle := defaultStyle.Foreground(tcell.ColorGray)
  235. for gIdx, gCh := range gutterText {
  236. if gIdx >= rect.Width {
  237. break
  238. }
  239. m.Screen.SetContent(rect.X+gIdx, rect.Y+j, gCh, nil, gutterStyle)
  240. }
  241. m.DrawStyledLine(
  242. rect.X+len(gutterText),
  243. rect.Y+j,
  244. line,
  245. currentLineByteOffset,
  246. rect.Width-len(gutterText),
  247. buf.Spans,
  248. defaultStyle,
  249. tabWidth,
  250. )
  251. currentLineByteOffset += uint32(len([]byte(line)))
  252. }
  253. if isActive {
  254. for _, cur := range buf.CM.Cursors {
  255. line, col := buffer.LineCol(buf, cur.Offset)
  256. screenLine := line - buf.TopLine
  257. if screenLine < 0 || screenLine+1 > rect.Height {
  258. continue
  259. }
  260. visCol := visualCol(buffer.LineText(buf, line), col)
  261. if visCol+1 > rect.Width {
  262. continue
  263. }
  264. ch := buffer.RuneAt(buf, cur.Offset)
  265. cursorBg := tcell.GetColor(m.Config.Colors.CursorColor)
  266. if m.Mode == ModeWrite {
  267. cursorBg = tcell.GetColor(m.Config.Colors.CursorColorWrite)
  268. }
  269. m.Screen.SetContent(
  270. rect.X+visCol+m.Config.Properties.GutterWidth+1,
  271. rect.Y+screenLine,
  272. ch,
  273. nil,
  274. defaultStyle.Background(cursorBg),
  275. )
  276. }
  277. }
  278. }
  279. func (m *Model) DrawStyledLine(startX, y int, rawLine string, lineStartByte uint32, maxWidth int, spans []highlight.HighlightSpan, defaultStyle tcell.Style, tabWidth int) {
  280. currentByte := lineStartByte
  281. screenCol := 0
  282. for _, ch := range rawLine {
  283. if screenCol >= maxWidth {
  284. break
  285. }
  286. chByteLen := uint32(len(string(ch)))
  287. activeStyle := defaultStyle
  288. for _, span := range spans {
  289. if currentByte >= span.StartByte && currentByte < span.EndByte {
  290. activeStyle = span.Style
  291. break
  292. }
  293. }
  294. if ch == '\t' {
  295. spacesToDraw := tabWidth - (screenCol % tabWidth)
  296. for s := 0; s < spacesToDraw; s++ {
  297. if screenCol >= maxWidth {
  298. break
  299. }
  300. m.Screen.SetContent(startX+screenCol, y, ' ', nil, activeStyle)
  301. screenCol++
  302. }
  303. } else {
  304. m.Screen.SetContent(startX+screenCol, y, ch, nil, activeStyle)
  305. screenCol++
  306. }
  307. currentByte += chByteLen
  308. }
  309. }
  310. func expandTabs(s string) string {
  311. var b strings.Builder
  312. col := 0
  313. for _, r := range s {
  314. if r == '\t' {
  315. spaces := tabWidth - (col % tabWidth)
  316. b.WriteString(strings.Repeat(" ", spaces))
  317. col += spaces
  318. } else {
  319. b.WriteRune(r)
  320. col++
  321. }
  322. }
  323. return b.String()
  324. }
  325. func visualCol(lineText string, runeCol int) int {
  326. col := 0
  327. n := 0
  328. for _, r := range lineText {
  329. if n >= runeCol {
  330. break
  331. }
  332. if r == '\t' {
  333. col += tabWidth - (col % tabWidth)
  334. } else {
  335. col++
  336. }
  337. n++
  338. }
  339. return col
  340. }