draw.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. package editor
  2. import (
  3. "fmt"
  4. "math"
  5. "moose/internal/buffer"
  6. "moose/internal/layout"
  7. "moose/internal/util"
  8. "slices"
  9. "strconv"
  10. "strings"
  11. "github.com/gdamore/tcell/v3"
  12. )
  13. func (m *Model) Draw() {
  14. screen := layout.RectFromScren(m.Screen)
  15. main := layout.Rect{
  16. X: screen.X,
  17. Y: screen.Y,
  18. Width: screen.Width,
  19. Height: screen.Height - 2,
  20. }
  21. palette := layout.Rect{
  22. X: screen.X,
  23. Y: main.Height,
  24. Width: screen.Width,
  25. Height: 2,
  26. }
  27. copy := m.LM.Workspaces[m.LM.ActiveIdx]
  28. m.DrawWorkspace(&copy, main)
  29. m.DrawPalette(palette)
  30. }
  31. func (m *Model) generateChordStr() string {
  32. chordStr := ""
  33. if len(m.AM.RCM.Recorded) > 0 {
  34. chordStr += strings.Join(m.AM.RCM.Recorded, "") + " + "
  35. }
  36. if len(m.AM.CM.Recorded) > 0 {
  37. chordStr += strings.Join(m.AM.CM.Recorded, "+") + " +"
  38. }
  39. return chordStr
  40. }
  41. func (m *Model) DrawPalette(rect layout.Rect) {
  42. for col := 0; col < rect.Width; col++ {
  43. 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)))
  44. }
  45. modeStr := m.Mode.String()
  46. if m.Mode == ModePalette {
  47. if strings.HasPrefix(m.BM.PaletteBuffer.String(), "/") {
  48. modeStr += " (command)"
  49. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "?") {
  50. modeStr += " (find)"
  51. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "=") {
  52. modeStr += " (replace)"
  53. }
  54. }
  55. 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)))
  56. splitStr := ""
  57. if m.LM.CurrentSplit == layout.SplitHorizontal {
  58. splitStr = "H"
  59. } else {
  60. splitStr = "V"
  61. }
  62. 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)))
  63. populatedWorkspaces := []int{m.LM.ActiveIdx + 1}
  64. for workspaceIdx, workspace := range m.LM.Workspaces {
  65. if workspaceIdx == m.LM.ActiveIdx {
  66. continue
  67. }
  68. if !workspace.IsEmpty() {
  69. populatedWorkspaces = append(populatedWorkspaces, workspaceIdx+1)
  70. }
  71. }
  72. slices.Sort(populatedWorkspaces)
  73. strWorkspaces := make([]string, len(populatedWorkspaces))
  74. for i, v := range populatedWorkspaces {
  75. strWorkspaces[i] = strconv.Itoa(v)
  76. }
  77. workspacesStr := strings.Join(strWorkspaces, " ")
  78. m.Screen.PutStrStyled(1, rect.Y, workspacesStr, m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.WorkspaceForeground)).Background(tcell.GetColor(m.Config.Colors.WorkspaceBackground)))
  79. for idx, workspace := range populatedWorkspaces {
  80. if workspace == m.LM.ActiveIdx + 1 {
  81. 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)))
  82. }
  83. }
  84. chordStr := m.generateChordStr()
  85. 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)))
  86. 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)))
  87. if m.Mode == ModePalette {
  88. 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)))
  89. for _, cur := range m.BM.PaletteBuffer.CM.Cursors {
  90. line, col := buffer.LineCol(&m.BM.PaletteBuffer, cur.Offset)
  91. if line+rect.Y != rect.Y {
  92. continue
  93. }
  94. if col+1 > rect.Width {
  95. continue
  96. }
  97. ch := buffer.RuneAt(&m.BM.PaletteBuffer, cur.Offset)
  98. m.Screen.SetContent(col, rect.Y+1, ch, nil, m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.CursorColorWrite)))
  99. }
  100. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "moose.info:") {
  101. 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)))
  102. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "moose.warn:") {
  103. 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)))
  104. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "moose.error:") {
  105. 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)))
  106. }
  107. }
  108. func (m *Model) DrawWorkspace(w *layout.Workspace, rect layout.Rect) {
  109. m.DrawContainer(&w.RootContainer, rect)
  110. }
  111. func (m *Model) DrawContainer(c *layout.Container, rect layout.Rect) {
  112. length := util.NonNilLen(c.Children[:])
  113. if length == 0 {
  114. return
  115. }
  116. rect = layout.Rect{
  117. X: rect.X,
  118. Y: rect.Y,
  119. Width: rect.Width,
  120. Height: rect.Height,
  121. }
  122. rect = layout.RectDivide(rect, c.Split, length)
  123. for i, child := range c.Children {
  124. main := layout.RectDisplace(rect, c.Split, i)
  125. switch child.(type) {
  126. case layout.ContainerBuffers:
  127. {
  128. cb := child.(layout.ContainerBuffers)
  129. tabs := layout.Rect{
  130. X: main.X,
  131. Y: main.Y,
  132. Width: main.Width,
  133. Height: 1,
  134. }
  135. main.Y += 1
  136. main.Height -= 1
  137. m.DrawContainerTabs(&cb, tabs)
  138. m.DrawContainerBuffers(&cb, main)
  139. }
  140. case layout.Container:
  141. {
  142. c := child.(layout.Container)
  143. m.DrawContainer(&c, main)
  144. }
  145. }
  146. }
  147. }
  148. func (m *Model) DrawContainerTabs(c *layout.ContainerBuffers, rect layout.Rect) {
  149. tabs := []string{}
  150. activeTabIdx := -1
  151. for _, bufIdx := range c.Buffers {
  152. if bufIdx >= len(m.BM.Buffers) {
  153. continue
  154. }
  155. if c.ActiveIdx < len(c.Buffers) && c.Buffers[c.ActiveIdx] == bufIdx {
  156. activeTabIdx = len(tabs)
  157. }
  158. buf := m.BM.Buffers[bufIdx]
  159. if buf.Path == "" {
  160. tabs = append(tabs, "Buffer "+strconv.Itoa(bufIdx))
  161. } else {
  162. tabs = append(tabs, buf.Path)
  163. }
  164. }
  165. length := 0
  166. for i, tab := range tabs {
  167. if length+len(tab)+2 > rect.Width {
  168. 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)))
  169. return
  170. }
  171. style := m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.TabBackground)).Foreground(tcell.GetColor(m.Config.Colors.TabForeground))
  172. if i == activeTabIdx {
  173. style = style.Foreground(tcell.GetColor(m.Config.Colors.TabForegroundActive)).Background(tcell.GetColor(m.Config.Colors.TabBackgroundActive))
  174. }
  175. tabStr := strconv.Itoa(int(math.Abs(float64((activeTabIdx - i))))) + ": " + tab
  176. m.Screen.PutStrStyled(rect.X+length, rect.Y, tabStr, style)
  177. length += len(tabStr) + 1
  178. }
  179. }
  180. func (m *Model) DrawContainerBuffers(c *layout.ContainerBuffers, rect layout.Rect) {
  181. if c.ActiveIdx >= len(c.Buffers) {
  182. return
  183. }
  184. if c.Buffers[c.ActiveIdx] >= len(m.BM.Buffers) {
  185. return
  186. }
  187. m.DrawBuffer(&m.BM.Buffers[c.Buffers[c.ActiveIdx]], c.Buffers[c.ActiveIdx] == m.BM.CurrentIdx, rect)
  188. }
  189. func (m *Model) DrawBuffer(buf *buffer.Buffer, isActive bool, rect layout.Rect) {
  190. primary := buf.CM.Cursors[buf.CM.PrimaryIdx]
  191. curLine, _ := buffer.LineCol(buf, primary.Offset)
  192. buf.ScrollToShow(curLine, rect.Height)
  193. startOffset := buffer.OffsetForLine(buf, buf.TopLine)
  194. endOffset := buf.Rope.Len()
  195. if endLine := buf.TopLine + rect.Height; endLine < buffer.LineCount(buf) {
  196. endOffset = buffer.OffsetForLine(buf, endLine)
  197. }
  198. visible := string(buf.Rope.Slice(startOffset, endOffset))
  199. table := strings.SplitAfter(visible, "\n")
  200. for j, line := range table {
  201. if j+1 > rect.Height {
  202. break
  203. }
  204. lineNum := j + buf.TopLine
  205. relLine := lineNum - curLine
  206. nums := fmt.Sprintf("%*d ", m.Config.Properties.GutterWidth, int(math.Abs(float64(relLine))))
  207. r := []rune(nums + expandTabs(line))
  208. if len(r)+1 > rect.Width {
  209. r = r[:rect.Width]
  210. }
  211. m.Screen.PutStrStyled(rect.X, rect.Y+j, string(r), m.Config.StyleDefault.Foreground(tcell.GetColor(m.Config.Colors.MainForeground)).Background(tcell.GetColor(m.Config.Colors.MainBackground)))
  212. }
  213. if isActive {
  214. for _, cur := range buf.CM.Cursors {
  215. line, col := buffer.LineCol(buf, cur.Offset)
  216. screenLine := line - buf.TopLine
  217. if screenLine < 0 || screenLine+1 > rect.Height {
  218. continue
  219. }
  220. visCol := visualCol(buffer.LineText(buf, line), col)
  221. if visCol+1 > rect.Width {
  222. continue
  223. }
  224. ch := buffer.RuneAt(buf, cur.Offset)
  225. if m.Mode == ModeWrite {
  226. m.Screen.SetContent(rect.X+visCol+m.Config.Properties.GutterWidth+1, rect.Y+screenLine, ch, nil, m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.CursorColorWrite)))
  227. } else {
  228. m.Screen.SetContent(rect.X+visCol+m.Config.Properties.GutterWidth+1, rect.Y+screenLine, ch, nil, m.Config.StyleDefault.Background(tcell.GetColor(m.Config.Colors.CursorColor)))
  229. }
  230. }
  231. }
  232. }
  233. const tabWidth = 4
  234. func expandTabs(s string) string {
  235. var b strings.Builder
  236. col := 0
  237. for _, r := range s {
  238. if r == '\t' {
  239. spaces := tabWidth - (col % tabWidth)
  240. b.WriteString(strings.Repeat(" ", spaces))
  241. col += spaces
  242. } else {
  243. b.WriteRune(r)
  244. col++
  245. }
  246. }
  247. return b.String()
  248. }
  249. func visualCol(lineText string, runeCol int) int {
  250. col := 0
  251. n := 0
  252. for _, r := range lineText {
  253. if n >= runeCol {
  254. break
  255. }
  256. if r == '\t' {
  257. col += tabWidth - (col % tabWidth)
  258. } else {
  259. col++
  260. }
  261. n++
  262. }
  263. return col
  264. }