All checks were successful
Run Test Suite / test (push) Successful in 31s
However, the colorscheme functions and tests do not work anymore, they need to be rebuilt.
422 lines
8.7 KiB
Go
422 lines
8.7 KiB
Go
package editor
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.gophernest.net/azpect/TextEditor/internal/action"
|
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
|
"git.gophernest.net/azpect/TextEditor/internal/input"
|
|
"git.gophernest.net/azpect/TextEditor/internal/syntax"
|
|
"git.gophernest.net/azpect/TextEditor/internal/theme"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
type Model struct {
|
|
// Buffers
|
|
buffers []*core.Buffer
|
|
//next buffer id?
|
|
|
|
// Windows
|
|
windows []*core.Window
|
|
activeWindowId int
|
|
|
|
// Editor wide state
|
|
mode core.Mode
|
|
|
|
// Terminal dimensions
|
|
termWidth int
|
|
termHeight int
|
|
|
|
// Input and key handling
|
|
input *input.Handler
|
|
|
|
// Insert mode state & repetition (applied to active window)
|
|
insertCount int
|
|
insertKeys []string
|
|
insertAction action.Action
|
|
lastFind core.LastFindCommand
|
|
|
|
// Command line state
|
|
command string
|
|
commandCursor int
|
|
commandOutput *core.CommandOutput
|
|
commandHistory []string
|
|
commandHistoryCursor int
|
|
|
|
// Global settings
|
|
settings core.EditorSettings
|
|
|
|
// Registers
|
|
registers map[rune]core.Register // name -> register
|
|
|
|
// Visual styles
|
|
theme theme.EditorTheme
|
|
syntax syntax.Engine
|
|
|
|
// Dot operator state
|
|
lastChangeKeys []string
|
|
}
|
|
|
|
// Model.Init: Initialize the model and start any commands that may need to run. Required
|
|
// for the bubbletea architecture.
|
|
func (m Model) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
// Implement action.Model interface
|
|
|
|
// ==================================================
|
|
// Core Data Access
|
|
// ==================================================
|
|
func (m *Model) Windows() []*core.Window {
|
|
return m.windows
|
|
}
|
|
|
|
func (m *Model) ActiveWindow() *core.Window {
|
|
winId := m.activeWindowId
|
|
for i := range m.Windows() {
|
|
if m.windows[i].Id == winId {
|
|
return m.windows[i]
|
|
}
|
|
}
|
|
panic("Could not find window")
|
|
}
|
|
|
|
func (m *Model) Buffers() []*core.Buffer {
|
|
return m.buffers
|
|
}
|
|
|
|
func (m *Model) SetBuffers(bufs []*core.Buffer) {
|
|
m.buffers = bufs
|
|
m.bindBufferSyntaxHooks(bufs)
|
|
}
|
|
|
|
func (m *Model) ActiveBuffer() *core.Buffer {
|
|
win := m.ActiveWindow()
|
|
return win.Buffer
|
|
}
|
|
|
|
// ==================================================
|
|
// Insert Mode Methods
|
|
// ==================================================
|
|
func (m *Model) InsertKeys() []string {
|
|
return m.insertKeys
|
|
}
|
|
|
|
func (m *Model) SetInsertKeys(keys []string) {
|
|
m.insertKeys = keys
|
|
}
|
|
|
|
func (m *Model) SetInsertRecording(count int, act action.Action) {
|
|
m.insertCount = count
|
|
m.insertKeys = []string{}
|
|
m.insertAction = act
|
|
}
|
|
|
|
func (m *Model) SetLastFind(char string, forward, inclusive bool) {
|
|
m.lastFind = core.LastFindCommand{
|
|
Char: char,
|
|
Forward: forward,
|
|
Inclusive: inclusive,
|
|
}
|
|
}
|
|
|
|
func (m *Model) GetLastFind() *core.LastFindCommand {
|
|
return &m.lastFind
|
|
}
|
|
|
|
// Does update the '.' register
|
|
func (m *Model) SetLastChangeKeys(keys []string) {
|
|
m.lastChangeKeys = keys
|
|
|
|
m.SetRegister('.', core.CharwiseRegister, []string{strings.Join(keys, "")})
|
|
}
|
|
|
|
func (m *Model) LastChangeKeys() []string {
|
|
return m.lastChangeKeys
|
|
}
|
|
|
|
func (m *Model) ClearLastChangeKeys() {
|
|
m.lastChangeKeys = []string{}
|
|
}
|
|
|
|
func (m *Model) HandleKey(key string) tea.Cmd {
|
|
return m.input.Handle(m, key)
|
|
}
|
|
|
|
func (m *Model) ExitInsertMode() {
|
|
win := m.ActiveWindow()
|
|
if m.insertCount > 1 {
|
|
m.replayInsert()
|
|
}
|
|
if win.Cursor.Col > 0 {
|
|
win.Cursor.Col--
|
|
}
|
|
m.mode = core.NormalMode
|
|
m.insertCount = 0
|
|
m.insertKeys = nil
|
|
}
|
|
|
|
func (m *Model) replayInsert() {
|
|
win := m.ActiveWindow()
|
|
buf := m.ActiveBuffer()
|
|
|
|
// Replay (count - 1) more times
|
|
for i := 1; i < m.insertCount; i++ {
|
|
// For 'o' and 'O', we need to create a new line first
|
|
switch m.insertAction.(type) {
|
|
case action.OpenLineBelow:
|
|
pos := win.Cursor.Line
|
|
buf.InsertLine(pos+1, "")
|
|
win.SetCursorLine(pos + 1)
|
|
|
|
case action.OpenLineAbove:
|
|
pos := win.Cursor.Line
|
|
buf.InsertLine(pos, "")
|
|
|
|
// 'i' and 'a' don't need setup - just replay keys
|
|
}
|
|
|
|
// Replay each recorded keystroke
|
|
for _, key := range m.insertKeys {
|
|
m.processInsertKey(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Fix this shitty shit shit shit
|
|
func (m *Model) processInsertKey(key string) {
|
|
win := m.ActiveWindow()
|
|
buf := m.ActiveBuffer()
|
|
|
|
col := win.Cursor.Col
|
|
line := win.Cursor.Line
|
|
l := buf.Line(line)
|
|
|
|
switch key {
|
|
case "enter":
|
|
if col == len(l) {
|
|
buf.InsertLine(line+1, "")
|
|
} else {
|
|
buf.SetLine(line, l[:col])
|
|
buf.InsertLine(line+1, l[col:])
|
|
}
|
|
win.SetCursorLine(line + 1)
|
|
win.SetCursorCol(0)
|
|
|
|
case "backspace":
|
|
if col > 0 {
|
|
buf.SetLine(line, l[:col-1]+l[col:])
|
|
win.SetCursorCol(col - 1)
|
|
} else if line > 0 {
|
|
prevLine := buf.Line(line - 1)
|
|
newCol := len(prevLine)
|
|
buf.SetLine(line-1, prevLine+l)
|
|
buf.DeleteLine(line)
|
|
win.SetCursorLine(line - 1)
|
|
win.SetCursorCol(newCol)
|
|
}
|
|
|
|
case "delete":
|
|
if col == len(l) && line < buf.LineCount()-1 {
|
|
nextLine := buf.Line(line + 1)
|
|
buf.SetLine(line, l+nextLine)
|
|
buf.DeleteLine(line + 1)
|
|
} else if col < len(l) {
|
|
buf.SetLine(line, l[:col]+l[col+1:])
|
|
}
|
|
|
|
case "tab":
|
|
tabs := strings.Repeat(" ", m.Settings().TabStop)
|
|
if col < len(l) {
|
|
buf.SetLine(line, l[:col]+tabs+l[col:])
|
|
} else {
|
|
buf.SetLine(line, l+tabs)
|
|
}
|
|
win.SetCursorCol(col + len(tabs))
|
|
|
|
case "up":
|
|
if line > 0 {
|
|
win.SetCursorLine(line - 1)
|
|
}
|
|
|
|
case "down":
|
|
if line+1 < buf.LineCount() {
|
|
win.SetCursorLine(line + 1)
|
|
}
|
|
|
|
case "left":
|
|
if col > 0 {
|
|
win.SetCursorCol(col - 1)
|
|
} else if line > 0 {
|
|
prevLine := buf.Line(line - 1)
|
|
win.SetCursorCol(len(prevLine))
|
|
win.SetCursorLine(line - 1)
|
|
}
|
|
|
|
case "right":
|
|
if col < len(l) {
|
|
win.SetCursorCol(col + 1)
|
|
} else if line+1 < buf.LineCount() {
|
|
win.SetCursorCol(0)
|
|
win.SetCursorLine(line + 1)
|
|
}
|
|
|
|
default:
|
|
if col < len(l) {
|
|
buf.SetLine(line, l[:col]+key+l[col:])
|
|
} else {
|
|
buf.SetLine(line, l+key)
|
|
}
|
|
win.SetCursorCol(col + len(key))
|
|
}
|
|
}
|
|
|
|
// ==================================================
|
|
// Command Mode State
|
|
// ==================================================
|
|
func (m *Model) Command() string {
|
|
return m.command
|
|
}
|
|
|
|
func (m *Model) SetCommand(cmd string) {
|
|
m.command = cmd
|
|
}
|
|
|
|
func (m *Model) CommandCursor() int {
|
|
return m.commandCursor
|
|
}
|
|
|
|
func (m *Model) SetCommandCursor(cur int) {
|
|
if cur < 0 {
|
|
m.commandCursor = 0
|
|
} else if cur >= len(m.command) {
|
|
m.commandCursor = len(m.command)
|
|
} else {
|
|
m.commandCursor = cur
|
|
}
|
|
}
|
|
|
|
func (m *Model) CommandOutput() *core.CommandOutput {
|
|
return m.commandOutput
|
|
}
|
|
|
|
func (m *Model) SetCommandOutput(out *core.CommandOutput) {
|
|
m.commandOutput = out
|
|
}
|
|
|
|
func (m *Model) CommandHistory() []string {
|
|
return m.commandHistory
|
|
}
|
|
|
|
func (m *Model) SetCommandHistory(history []string) {
|
|
m.commandHistory = history
|
|
}
|
|
|
|
func (m *Model) CommandHistoryCursor() int {
|
|
return m.commandHistoryCursor
|
|
}
|
|
|
|
func (m *Model) SetCommandHistoryCursor(cur int) {
|
|
m.commandHistoryCursor = cur
|
|
}
|
|
|
|
// ==================================================
|
|
// Editor-wide State
|
|
// ==================================================
|
|
func (m *Model) Mode() core.Mode {
|
|
return m.mode
|
|
}
|
|
|
|
func (m *Model) SetMode(mode core.Mode) {
|
|
m.mode = mode
|
|
}
|
|
|
|
func (m *Model) Settings() core.EditorSettings {
|
|
return m.settings
|
|
}
|
|
|
|
func (m *Model) SetSettings(s core.EditorSettings) {
|
|
m.settings = s
|
|
}
|
|
|
|
func (m *Model) Theme() theme.EditorTheme {
|
|
return m.theme
|
|
}
|
|
|
|
func (m *Model) SetTheme(t theme.EditorTheme) {
|
|
m.theme = t
|
|
}
|
|
|
|
func (m *Model) Syntax() syntax.Engine {
|
|
return m.syntax
|
|
}
|
|
|
|
func (m *Model) SetSyntax(s syntax.Engine) {
|
|
m.syntax = s
|
|
m.bindBufferSyntaxHooks(m.buffers)
|
|
}
|
|
|
|
func (m *Model) bindBufferSyntaxHooks(bufs []*core.Buffer) {
|
|
if m.syntax == nil {
|
|
return
|
|
}
|
|
|
|
for _, buf := range bufs {
|
|
if buf == nil {
|
|
continue
|
|
}
|
|
|
|
b := buf
|
|
b.OnChange = func(change core.BufferChange) {
|
|
if change.Edit != nil {
|
|
m.syntax.ApplyEdit(b, change.Edit)
|
|
return
|
|
}
|
|
|
|
switch change.Kind {
|
|
case core.BufferChangeSetLine:
|
|
m.syntax.InvalidateLines(b, change.StartLine, change.EndLine)
|
|
default:
|
|
m.syntax.InvalidateBuffer(b)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==================================================
|
|
// Registers
|
|
// ==================================================
|
|
func (m *Model) Registers() map[rune]core.Register {
|
|
return m.registers
|
|
}
|
|
|
|
func (m *Model) GetRegister(name rune) (core.Register, bool) {
|
|
reg, found := m.registers[name]
|
|
return reg, found
|
|
}
|
|
|
|
func (m *Model) SetRegister(name rune, t core.RegisterType, cnt []string) error {
|
|
if _, found := m.GetRegister(name); !found {
|
|
return fmt.Errorf("Register '%c' does not exist.", name)
|
|
}
|
|
|
|
// TODO: This might be slow, pointers maybe?
|
|
reg := core.Register{Type: t, Content: cnt}
|
|
m.registers[name] = reg
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Model) UpdateDefaultRegister(t core.RegisterType, cnt []string) {
|
|
// Shift numbered registers: 0 -> 1 -> 2 -> ... -> 9 -> _ (discarded)
|
|
for i := rune('9'); i > '0'; i-- {
|
|
m.registers[i] = m.registers[i-1]
|
|
}
|
|
|
|
// 0 and " both hold the new content independently
|
|
m.SetRegister('0', t, cnt)
|
|
m.SetRegister('"', t, cnt)
|
|
}
|