render.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package editor
  2. import (
  3. "strings"
  4. "strconv"
  5. "fmt"
  6. "moose/internal/buffer"
  7. "github.com/gdamore/tcell/v3/color"
  8. )
  9. const tabWidth = 4
  10. func expandTabs(s string) string {
  11. var b strings.Builder
  12. col := 0
  13. for _, r := range s {
  14. if r == '\t' {
  15. spaces := tabWidth - (col % tabWidth)
  16. b.WriteString(strings.Repeat(" ", spaces))
  17. col += spaces
  18. } else {
  19. b.WriteRune(r)
  20. col++
  21. }
  22. }
  23. return b.String()
  24. }
  25. func visualCol(lineText string, runeCol int) int {
  26. col := 0
  27. n := 0
  28. for _, r := range lineText {
  29. if n >= runeCol {
  30. break
  31. }
  32. if r == '\t' {
  33. col += tabWidth - (col % tabWidth)
  34. } else {
  35. col++
  36. }
  37. n++
  38. }
  39. return col
  40. }
  41. func (m Model) Render() {
  42. sWidth, sHeight := m.Screen.Size()
  43. maxHeight := sHeight - 2 // TODO: saturating sub?
  44. bLen := len(m.BM.Buffers)
  45. bufWidth := sWidth / bLen
  46. for i := range m.BM.Buffers {
  47. buf := &m.BM.Buffers[i]
  48. primary := buf.CM.Cursors[buf.CM.PrimaryIdx]
  49. curLine, _ := buffer.LineCol(buf, primary.Offset)
  50. buf.ScrollToShow(curLine, maxHeight)
  51. startOffset := buffer.OffsetForLine(buf, buf.TopLine)
  52. endOffset := buf.Rope.Len()
  53. if endLine := buf.TopLine + maxHeight; endLine < buffer.LineCount(buf) {
  54. endOffset = buffer.OffsetForLine(buf, endLine)
  55. }
  56. visible := string(buf.Rope.Slice(startOffset, endOffset))
  57. table := strings.SplitAfter(visible, "\n")
  58. for j, line := range table {
  59. if j + 1 > maxHeight { break }
  60. nums := fmt.Sprintf("%4d ", j + buf.TopLine)
  61. r := []rune(nums + expandTabs(line))
  62. if len(r) + 1 > bufWidth {
  63. r = r[:bufWidth]
  64. }
  65. m.Screen.PutStrStyled(bufWidth*i, j, string(r), m.Style)
  66. }
  67. if i == m.BM.CurrentIdx {
  68. for _, cur := range buf.CM.Cursors {
  69. line, col := buffer.LineCol(buf, cur.Offset)
  70. screenLine := line - buf.TopLine
  71. if screenLine < 0 || screenLine + 1 > maxHeight { continue }
  72. visCol := visualCol(buffer.LineText(buf, line), col)
  73. if visCol + 1 > bufWidth { continue }
  74. ch := buffer.RuneAt(buf, cur.Offset)
  75. if m.Mode == ModeWrite {
  76. m.Screen.SetContent(bufWidth * i + visCol + 5, screenLine, ch, nil, m.Style.Reverse(true))
  77. } else {
  78. m.Screen.SetContent(bufWidth * i + visCol + 5, screenLine, ch, nil, m.Style.Reverse(true).Foreground(color.Gray))
  79. }
  80. }
  81. }
  82. }
  83. for col := 0; col < sWidth; col++ {
  84. m.Screen.SetContent(col, maxHeight, ' ', nil, m.Style.Background(color.Black))
  85. }
  86. modeStr := m.Mode.String()
  87. if m.Mode == ModePalette {
  88. if strings.HasPrefix(m.BM.PaletteBuffer.String(), "/") {
  89. modeStr += " (command)"
  90. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "?") {
  91. modeStr += " (find)"
  92. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "=") {
  93. modeStr += " (replace)"
  94. }
  95. }
  96. m.Screen.PutStrStyled(sWidth - (len(modeStr) + 1), maxHeight, strings.ToUpper(modeStr), m.Style.Background(color.Black))
  97. logStr := m.DebugLog + ", " + strings.Join(m.AM.CM.Recorded, "+") + ", " + strconv.FormatBool(m.AM.CM.Recording)
  98. m.Screen.PutStrStyled(1, maxHeight, logStr, m.Style.Background(color.Black))
  99. if m.Mode == ModePalette {
  100. m.Screen.PutStrStyled(0, maxHeight + 1, m.BM.PaletteBuffer.String(), m.Style)
  101. for _, cur := range m.BM.PaletteBuffer.CM.Cursors {
  102. line, col := buffer.LineCol(&m.BM.PaletteBuffer, cur.Offset)
  103. if line + maxHeight != maxHeight { continue }
  104. if col + 1 > sWidth { continue }
  105. ch := buffer.RuneAt(&m.BM.PaletteBuffer, cur.Offset)
  106. m.Screen.SetContent(col, maxHeight + 1, ch, nil, m.Style.Reverse(true))
  107. }
  108. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "moose.info:") {
  109. m.Screen.PutStrStyled(0, maxHeight + 1, string([]rune(m.BM.PaletteBuffer.String())[11:]), m.Style.Foreground(color.LightGray))
  110. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "moose.warn:") {
  111. m.Screen.PutStrStyled(0, maxHeight + 1, string([]rune(m.BM.PaletteBuffer.String())[11:]), m.Style.Foreground(color.Orange))
  112. } else if strings.HasPrefix(m.BM.PaletteBuffer.String(), "moose.error:") {
  113. m.Screen.PutStrStyled(0, maxHeight + 1, string([]rune(m.BM.PaletteBuffer.String())[12:]), m.Style.Foreground(color.Red))
  114. }
  115. }