draw.go 7.4 KB

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