Compare commits
No commits in common. "6b8845b245724555a647fe1852db4572b605c3b5" and "edbed61949626a6545396a12ed31f2aa33a26cd2" have entirely different histories.
6b8845b245
...
edbed61949
@ -12,7 +12,8 @@ type ExitCommandMode struct{}
|
|||||||
func (a ExitCommandMode) Execute(m Model) tea.Cmd {
|
func (a ExitCommandMode) Execute(m Model) tea.Cmd {
|
||||||
m.SetCommandCursor(0)
|
m.SetCommandCursor(0)
|
||||||
m.SetCommand("")
|
m.SetCommand("")
|
||||||
m.SetCommandOutput(&core.CommandOutput{})
|
m.SetCommandOutput("")
|
||||||
|
m.SetCommandError(nil)
|
||||||
m.SetMode(core.NormalMode)
|
m.SetMode(core.NormalMode)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -127,6 +128,7 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
|
|||||||
|
|
||||||
// Clear command state and return to normal mode
|
// Clear command state and return to normal mode
|
||||||
m.SetCommandCursor(0)
|
m.SetCommandCursor(0)
|
||||||
|
m.SetCommandError(nil)
|
||||||
m.SetMode(core.NormalMode)
|
m.SetMode(core.NormalMode)
|
||||||
|
|
||||||
if a.Registry == nil || cmdLine == "" {
|
if a.Registry == nil || cmdLine == "" {
|
||||||
@ -135,12 +137,7 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
|
|||||||
|
|
||||||
cmd, err := a.Registry.Execute(m, cmdLine)
|
cmd, err := a.Registry.Execute(m, cmdLine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out := core.CommandOutput{
|
m.SetCommandError(err)
|
||||||
Lines: []string{err.Error()},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
m.SetCommandOutput(&out)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,8 @@ type mockModel struct {
|
|||||||
insertKeys []string
|
insertKeys []string
|
||||||
command string
|
command string
|
||||||
commandCursor int
|
commandCursor int
|
||||||
commandOutput *core.CommandOutput
|
commandError error
|
||||||
|
commandOutput string
|
||||||
lastFind core.LastFindCommand
|
lastFind core.LastFindCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,12 +92,14 @@ func (m *mockModel) SetLastFind(char string, forward, inclusive bool) {
|
|||||||
func (m *mockModel) GetLastFind() *core.LastFindCommand { return &m.lastFind }
|
func (m *mockModel) GetLastFind() *core.LastFindCommand { return &m.lastFind }
|
||||||
|
|
||||||
// Command Mode State
|
// Command Mode State
|
||||||
func (m *mockModel) Command() string { return m.command }
|
func (m *mockModel) Command() string { return m.command }
|
||||||
func (m *mockModel) SetCommand(cmd string) { m.command = cmd }
|
func (m *mockModel) SetCommand(cmd string) { m.command = cmd }
|
||||||
func (m *mockModel) CommandCursor() int { return m.commandCursor }
|
func (m *mockModel) CommandCursor() int { return m.commandCursor }
|
||||||
func (m *mockModel) SetCommandCursor(cur int) { m.commandCursor = cur }
|
func (m *mockModel) SetCommandCursor(cur int) { m.commandCursor = cur }
|
||||||
func (m *mockModel) CommandOutput() *core.CommandOutput { return m.commandOutput }
|
func (m *mockModel) CommandError() error { return m.commandError }
|
||||||
func (m *mockModel) SetCommandOutput(out *core.CommandOutput) { m.commandOutput = out }
|
func (m *mockModel) SetCommandError(err error) { m.commandError = err }
|
||||||
|
func (m *mockModel) CommandOutput() string { return m.commandOutput }
|
||||||
|
func (m *mockModel) SetCommandOutput(out string) { m.commandOutput = out }
|
||||||
|
|
||||||
// Editor-wide State
|
// Editor-wide State
|
||||||
func (m *mockModel) Mode() core.Mode { return m.mode }
|
func (m *mockModel) Mode() core.Mode { return m.mode }
|
||||||
|
|||||||
@ -37,8 +37,10 @@ type Model interface {
|
|||||||
SetCommand(cmd string)
|
SetCommand(cmd string)
|
||||||
CommandCursor() int
|
CommandCursor() int
|
||||||
SetCommandCursor(cur int)
|
SetCommandCursor(cur int)
|
||||||
CommandOutput() *core.CommandOutput
|
CommandError() error
|
||||||
SetCommandOutput(out *core.CommandOutput)
|
SetCommandError(err error)
|
||||||
|
CommandOutput() string
|
||||||
|
SetCommandOutput(out string)
|
||||||
|
|
||||||
// ==================================================
|
// ==================================================
|
||||||
// Editor-wide State
|
// Editor-wide State
|
||||||
|
|||||||
@ -20,7 +20,8 @@ type EnterComandMode struct{}
|
|||||||
func (a EnterComandMode) Execute(m Model) tea.Cmd {
|
func (a EnterComandMode) Execute(m Model) tea.Cmd {
|
||||||
m.SetMode(core.CommandMode)
|
m.SetMode(core.CommandMode)
|
||||||
m.SetCommand("")
|
m.SetCommand("")
|
||||||
m.SetCommandOutput(&core.CommandOutput{})
|
m.SetCommandOutput("")
|
||||||
|
m.SetCommandError(nil)
|
||||||
m.SetCommandCursor(0)
|
m.SetCommandCursor(0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package action
|
package action
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
@ -20,12 +21,7 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
|||||||
// Get reg
|
// Get reg
|
||||||
reg, found := m.GetRegister('"')
|
reg, found := m.GetRegister('"')
|
||||||
if !found {
|
if !found {
|
||||||
out := core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("\"\" register is broken. Uh oh."))
|
||||||
Lines: []string{"\"\" register is broken. Uh oh."},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
m.SetCommandOutput(&out)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,12 +55,7 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
|||||||
|
|
||||||
// Shouldn't happen, just a check
|
// Shouldn't happen, just a check
|
||||||
if len(lines) != 1 {
|
if len(lines) != 1 {
|
||||||
out := core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("Charwise register should only have a single line of content."))
|
||||||
Lines: []string{"Charwise register should only have a single line of content."},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
m.SetCommandOutput(&out)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,12 +73,7 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
|||||||
win.SetCursorCol(x + len(cnt))
|
win.SetCursorCol(x + len(cnt))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
out := core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("core.Register type is not implemented."))
|
||||||
Lines: []string{"core.Register type is not implemented."},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
m.SetCommandOutput(&out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -114,12 +100,7 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
|||||||
// Get reg
|
// Get reg
|
||||||
reg, found := m.GetRegister('"')
|
reg, found := m.GetRegister('"')
|
||||||
if !found {
|
if !found {
|
||||||
out := core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("\"\" register is broken. Uh oh."))
|
||||||
Lines: []string{"\"\" register is broken. Uh oh."},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
m.SetCommandOutput(&out)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,12 +125,7 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
|||||||
|
|
||||||
// Shouldn't happen, just a check
|
// Shouldn't happen, just a check
|
||||||
if len(lines) != 1 {
|
if len(lines) != 1 {
|
||||||
out := core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("Charwise register should only have a single line of content."))
|
||||||
Lines: []string{"Charwise register should only have a single line of content."},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
m.SetCommandOutput(&out)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,12 +143,7 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
|||||||
win.SetCursorCol(x + len(cnt))
|
win.SetCursorCol(x + len(cnt))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
out := core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("core.Register type is not implemented."))
|
||||||
Lines: []string{"core.Register type is not implemented."},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
m.SetCommandOutput(&out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -196,12 +167,7 @@ func (a VisualPaste) Execute(m Model) tea.Cmd {
|
|||||||
// Get register content to paste
|
// Get register content to paste
|
||||||
reg, found := m.GetRegister('"')
|
reg, found := m.GetRegister('"')
|
||||||
if !found {
|
if !found {
|
||||||
out := core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("\"\" register is broken. Uh oh."))
|
||||||
Lines: []string{"\"\" register is broken. Uh oh."},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
}
|
|
||||||
m.SetCommandOutput(&out)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,11 +38,7 @@ func cmdQuit(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
// Cannot exit if any buffer has unsaved changes
|
// Cannot exit if any buffer has unsaved changes
|
||||||
for _, buf := range bufs {
|
for _, buf := range bufs {
|
||||||
if buf.Modified {
|
if buf.Modified {
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("unsaved changes to '%s'", buf.Filename))
|
||||||
Lines: []string{fmt.Sprintf("unsaved changes to '%s'", buf.Filename)},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
m.ActiveWindow().SetBuffer(buf)
|
m.ActiveWindow().SetBuffer(buf)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -66,11 +62,7 @@ func cmdWrite(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
buf := m.ActiveBuffer()
|
buf := m.ActiveBuffer()
|
||||||
cmd, err := writeBuffer(m, buf, args, force)
|
cmd, err := writeBuffer(m, buf, args, force)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(err)
|
||||||
Lines: []string{err.Error()},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@ -84,11 +76,7 @@ func cmdWriteAll(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
if buf.Modified {
|
if buf.Modified {
|
||||||
cmd, err := writeBuffer(m, buf, args, force)
|
cmd, err := writeBuffer(m, buf, args, force)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(err)
|
||||||
Lines: []string{err.Error()},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
@ -103,11 +91,7 @@ func cmdWriteQuit(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
buf := m.ActiveBuffer()
|
buf := m.ActiveBuffer()
|
||||||
cmd, err := writeBuffer(m, buf, args, force)
|
cmd, err := writeBuffer(m, buf, args, force)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(err)
|
||||||
Lines: []string{err.Error()},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,11 +108,7 @@ func cmdWriteQuitAll(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
if buf.Modified {
|
if buf.Modified {
|
||||||
cmd, err := writeBuffer(m, buf, args, force)
|
cmd, err := writeBuffer(m, buf, args, force)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(err)
|
||||||
Lines: []string{err.Error()},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
@ -143,11 +123,7 @@ func cmdWriteQuitAll(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
func cmdEdit(m action.Model, args []string, force bool) tea.Cmd {
|
func cmdEdit(m action.Model, args []string, force bool) tea.Cmd {
|
||||||
// must have arguments, cant edit nothing
|
// must have arguments, cant edit nothing
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(fmt.Errorf(":edit requires an argument"))
|
||||||
Lines: []string{":edit requires an argument"},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,11 +156,7 @@ func cmdEdit(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
notFound := errors.Is(err, os.ErrNotExist)
|
notFound := errors.Is(err, os.ErrNotExist)
|
||||||
|
|
||||||
if err != nil && !notFound {
|
if err != nil && !notFound {
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(err)
|
||||||
Lines: []string{err.Error()},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if file != nil {
|
if file != nil {
|
||||||
@ -243,427 +215,29 @@ func cmdEdit(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
// Register Commands
|
// Register Commands
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
// cmdRegisters: Handles :register command
|
// cmdRegisters: Handles :register command (debug - displays register content).
|
||||||
func cmdRegisters(m action.Model, args []string, force bool) tea.Cmd {
|
func cmdRegisters(m action.Model, args []string, force bool) tea.Cmd {
|
||||||
|
// TODO: This is temporary, for debugging
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
regs := m.Registers()
|
m.SetCommandError(fmt.Errorf("Please provide a name. Register dump not yet implemented."))
|
||||||
lines := []string{"Type Name Content"}
|
|
||||||
|
|
||||||
for name, reg := range regs {
|
|
||||||
if len(reg.Content) > 0 {
|
|
||||||
line := fmt.Sprintf(
|
|
||||||
" %s \"%c %s",
|
|
||||||
reg.Type.ToString(),
|
|
||||||
name,
|
|
||||||
strings.Join(reg.Content, "\\n"),
|
|
||||||
)
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.SetMode(core.CommandOutputMode)
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Title: ":reg",
|
|
||||||
Lines: lines,
|
|
||||||
Inline: false,
|
|
||||||
IsError: false,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BUG: We can actually handle many now
|
if len(args[0]) != 1 {
|
||||||
// if len(args[0]) != 1 {
|
m.SetCommandError(fmt.Errorf("Name should be a single character."))
|
||||||
// m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
// Lines: []string{"Name should be a single character."},
|
|
||||||
// Inline: true,
|
|
||||||
// IsError: true,
|
|
||||||
// })
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
names := []rune(args[0])
|
|
||||||
lines := []string{"Type Name Content"}
|
|
||||||
|
|
||||||
for _, name := range names {
|
|
||||||
reg, ok := m.GetRegister(name)
|
|
||||||
if ok && len(reg.Content) > 0 {
|
|
||||||
line := fmt.Sprintf(
|
|
||||||
" %s \"%c %s",
|
|
||||||
reg.Type.ToString(),
|
|
||||||
name,
|
|
||||||
strings.Join(reg.Content, "\\n"),
|
|
||||||
)
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.SetMode(core.CommandOutputMode)
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Title: ":reg",
|
|
||||||
Lines: lines,
|
|
||||||
Inline: false,
|
|
||||||
IsError: false,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// Buffer Commands
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
// Switching
|
|
||||||
// - :b <n> — switch to buffer number n
|
|
||||||
// - :b <name> — switch by partial filename match
|
|
||||||
// - :bn — next buffer (wraps)
|
|
||||||
// - :bp — previous buffer (wraps, currently panics on wrap-back)
|
|
||||||
// - :bf — first buffer
|
|
||||||
// - :bl — last buffer
|
|
||||||
|
|
||||||
// Opening / closing
|
|
||||||
// - :e <file> — open file into new buffer
|
|
||||||
// - :bd — delete (unload) current buffer
|
|
||||||
// - :bd <n> — delete buffer n
|
|
||||||
// - :bw — wipe buffer completely // TODO: Implement this
|
|
||||||
|
|
||||||
// cmdListBuffers: Handles :buffers & :ls command. Lists the active buffers.
|
|
||||||
func cmdListBuffers(m action.Model, args []string, force bool) tea.Cmd {
|
|
||||||
_, _ = args, force
|
|
||||||
|
|
||||||
// What we should display
|
|
||||||
// ------------------------------
|
|
||||||
// - % — current buffer
|
|
||||||
// - # — alternate buffer (last active)
|
|
||||||
// - a — active (loaded and visible)
|
|
||||||
// - h — hidden (loaded but not visible)
|
|
||||||
// - + — modified (unsaved changes)
|
|
||||||
// - - — not modifiable
|
|
||||||
|
|
||||||
curBuf := m.ActiveBuffer()
|
|
||||||
bufs := m.Buffers()
|
|
||||||
var lines []string
|
|
||||||
for _, buf := range bufs {
|
|
||||||
// Skip unlisted buffers
|
|
||||||
if !buf.Listed {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var flags strings.Builder
|
|
||||||
if buf.Id == curBuf.Id {
|
|
||||||
flags.WriteRune('%')
|
|
||||||
} else {
|
|
||||||
flags.WriteRune(' ')
|
|
||||||
}
|
|
||||||
// TODO: Implement alternate buffer
|
|
||||||
|
|
||||||
// Cannot really display the a and h, since we don't have visible flags yet
|
|
||||||
// For now, we will have a loaded flag, 'l'
|
|
||||||
if buf.Loaded {
|
|
||||||
flags.WriteRune('l')
|
|
||||||
}
|
|
||||||
flags.WriteRune(' ')
|
|
||||||
if buf.Modified {
|
|
||||||
flags.WriteRune('+')
|
|
||||||
}
|
|
||||||
if buf.ReadOnly {
|
|
||||||
flags.WriteRune('-')
|
|
||||||
}
|
|
||||||
|
|
||||||
line := fmt.Sprintf("%3d %s \"%s\"", buf.Id, flags.String(), buf.Filename)
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.SetMode(core.CommandOutputMode)
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Title: ":buffers",
|
|
||||||
Lines: lines,
|
|
||||||
Inline: false,
|
|
||||||
IsError: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmdNextBuffer: Handles :bn command. Moves to the next buffer based on ID.
|
|
||||||
func cmdNextBuffer(m action.Model, args []string, force bool) tea.Cmd {
|
|
||||||
_, _ = args, force
|
|
||||||
|
|
||||||
bufs := m.Buffers()
|
|
||||||
curBuf := m.ActiveBuffer()
|
|
||||||
|
|
||||||
ids := make([]int, len(bufs))
|
|
||||||
var curIndex int
|
|
||||||
for i, buf := range bufs {
|
|
||||||
if buf.Listed {
|
|
||||||
ids[i] = buf.Id
|
|
||||||
if buf.Id == curBuf.Id {
|
|
||||||
curIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nextId := (curIndex + 1) % len(ids)
|
|
||||||
|
|
||||||
m.ActiveWindow().SetBuffer(bufs[nextId])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmdPrevBuffer: Handles :bp command. Moves to the previous buffer based on ID.
|
|
||||||
func cmdPrevBuffer(m action.Model, args []string, force bool) tea.Cmd {
|
|
||||||
_, _ = args, force
|
|
||||||
|
|
||||||
bufs := m.Buffers()
|
|
||||||
curBuf := m.ActiveBuffer()
|
|
||||||
|
|
||||||
ids := make([]int, len(bufs))
|
|
||||||
var curIndex int
|
|
||||||
for i, buf := range bufs {
|
|
||||||
if buf.Listed {
|
|
||||||
ids[i] = buf.Id
|
|
||||||
if buf.Id == curBuf.Id {
|
|
||||||
curIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevId := ((curIndex - 1) + len(ids)) % len(ids)
|
|
||||||
|
|
||||||
m.ActiveWindow().SetBuffer(bufs[prevId])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmdFirstBuffer: Handles :bf command. Moves to the first buffer based on ID.
|
|
||||||
func cmdFirstBuffer(m action.Model, args []string, force bool) tea.Cmd {
|
|
||||||
_, _ = args, force
|
|
||||||
|
|
||||||
bufs := m.Buffers()
|
|
||||||
|
|
||||||
ids := make([]int, len(bufs))
|
|
||||||
for i, buf := range bufs {
|
|
||||||
if buf.Listed {
|
|
||||||
ids[i] = buf.Id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.ActiveWindow().SetBuffer(bufs[0])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmdLastBuffer: Handles :bf command. Moves to the last buffer based on ID.
|
|
||||||
func cmdLastBuffer(m action.Model, args []string, force bool) tea.Cmd {
|
|
||||||
_, _ = args, force
|
|
||||||
|
|
||||||
bufs := m.Buffers()
|
|
||||||
|
|
||||||
ids := make([]int, len(bufs))
|
|
||||||
for i, buf := range bufs {
|
|
||||||
if buf.Listed {
|
|
||||||
ids[i] = buf.Id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.ActiveWindow().SetBuffer(bufs[len(bufs)-1])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmdSelectBuffer: Handles :b command. Moves to the selected buffer based on ID or filename.
|
|
||||||
func cmdSelectBuffer(m action.Model, args []string, force bool) tea.Cmd {
|
|
||||||
_ = force
|
|
||||||
|
|
||||||
// Cannot function without args
|
|
||||||
if len(args) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 1 {
|
name := rune(args[0][0])
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
reg, found := m.GetRegister(name)
|
||||||
Lines: []string{fmt.Sprintf("Trailing characters: %s", strings.Join(args[1:], " "))},
|
if !found {
|
||||||
Inline: true,
|
m.SetCommandError(fmt.Errorf("Could not find register '%c'.", name))
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bufs := m.Buffers()
|
content := strings.Join(reg.Content, "\\n")
|
||||||
|
t := reg.Type
|
||||||
// If we can parse the input as number, try an ID
|
m.SetCommandOutput(fmt.Sprintf("Type: %d Name: \"%c Content: %s", t, name, content))
|
||||||
tgtId, err := strconv.Atoi(args[0])
|
|
||||||
if err == nil {
|
|
||||||
for i, buf := range bufs {
|
|
||||||
if buf.Id == tgtId && buf.Listed {
|
|
||||||
m.ActiveWindow().SetBuffer(bufs[i])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Lines: []string{fmt.Sprintf("Buffer id %d does not exist", tgtId)},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, try to match using filename
|
|
||||||
query := args[0]
|
|
||||||
var matches []int
|
|
||||||
for i, buf := range bufs {
|
|
||||||
if strings.Contains(buf.Filename, query) && buf.Listed {
|
|
||||||
matches = append(matches, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(matches) == 0 {
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Lines: []string{fmt.Sprintf("No matches for for %s", query)},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(matches) > 1 {
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Lines: []string{fmt.Sprintf("More than one match for %s", query)},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
m.ActiveWindow().SetBuffer(bufs[matches[0]])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmdDeleteBuffer: Handles :bd command. Deletes (unloads) a buffer.
|
|
||||||
func cmdDeleteBuffer(m action.Model, args []string, force bool) tea.Cmd {
|
|
||||||
// This will be as dynamic as possible, just get a list of indexes, then unlist them all
|
|
||||||
var indexes []int
|
|
||||||
bufs := m.Buffers()
|
|
||||||
|
|
||||||
// If the deleted buffer was the active one, Vim switches to the most recent entry in the jump
|
|
||||||
// list that points into a loaded buffer. This is not simply "the previous buffer" — it's
|
|
||||||
// jump-list-based, so it could be any recently visited loaded buffer.
|
|
||||||
// THOUGH: I am not building vim, so it does not have to be the same
|
|
||||||
|
|
||||||
// Need to close any windows associated with the closed buffers. Once many windows are implemented.
|
|
||||||
|
|
||||||
// No args, unlist current buffer
|
|
||||||
if len(args) == 0 {
|
|
||||||
curBuf := m.ActiveBuffer()
|
|
||||||
|
|
||||||
if curBuf.Modified && !force {
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Lines: []string{fmt.Sprintf("No write since last change to buffer %d (Add ! to continue)", curBuf.Id)},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, buf := range bufs {
|
|
||||||
if buf.Id == curBuf.Id {
|
|
||||||
indexes = append(indexes, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) > 0 {
|
|
||||||
|
|
||||||
// Arg can be ID or name
|
|
||||||
ArgumentList:
|
|
||||||
for _, arg := range args {
|
|
||||||
// Try to get ID, if we can, move until we find it
|
|
||||||
id, err := strconv.Atoi(arg)
|
|
||||||
if err == nil {
|
|
||||||
for index, buf := range bufs {
|
|
||||||
if buf.Id == id && buf.Listed {
|
|
||||||
if buf.Modified && !force {
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Lines: []string{fmt.Sprintf("No write since last change to buffer %d (Add ! to continue)", buf.Id)},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
indexes = append(indexes, index)
|
|
||||||
continue ArgumentList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue ArgumentList
|
|
||||||
}
|
|
||||||
|
|
||||||
// Failed to parse, fuzzy match on names
|
|
||||||
var matches []int
|
|
||||||
for index, buf := range bufs {
|
|
||||||
if strings.Contains(buf.Filename, arg) && buf.Listed {
|
|
||||||
matches = append(matches, index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(matches) > 1 {
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Lines: []string{fmt.Sprintf("More than one match for %s", arg)},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(matches) > 0 {
|
|
||||||
|
|
||||||
if bufs[matches[0]].Modified && !force {
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Lines: []string{fmt.Sprintf(
|
|
||||||
"No write since last change to buffer %d (Add ! to continue)",
|
|
||||||
bufs[matches[0]].Id),
|
|
||||||
},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
indexes = append(indexes, matches[0])
|
|
||||||
continue ArgumentList
|
|
||||||
}
|
|
||||||
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Lines: []string{fmt.Sprintf("No matching buffer for %s", arg)},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple error output
|
|
||||||
if len(indexes) == 0 {
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
|
||||||
Lines: []string{"No buffers were deleted"},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can delete the buffers
|
|
||||||
for _, i := range indexes {
|
|
||||||
bufs[i].SetListed(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to first listed buffer
|
|
||||||
// TODO: Switch to alternate buffer if available, once implemented
|
|
||||||
if !m.ActiveBuffer().Listed {
|
|
||||||
for _, buf := range bufs {
|
|
||||||
if buf.Listed {
|
|
||||||
m.ActiveWindow().SetBuffer(buf)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -681,20 +255,14 @@ func cmdDeleteBuffer(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
// :set ts=4 - set tabstop to 4 (abbreviation)
|
// :set ts=4 - set tabstop to 4 (abbreviation)
|
||||||
func cmdSet(m action.Model, args []string, force bool) tea.Cmd {
|
func cmdSet(m action.Model, args []string, force bool) tea.Cmd {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
out := fmt.Sprintf("%+v", m.Settings())
|
||||||
Lines: []string{fmt.Sprintf("%+v", m.Settings())},
|
m.SetCommandOutput(out)
|
||||||
Inline: true,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if err := parseSetOption(m, arg); err != nil {
|
if err := parseSetOption(m, arg); err != nil {
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(err)
|
||||||
Lines: []string{err.Error()},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -75,10 +75,8 @@ func writeBuffer(m action.Model, buf *core.Buffer, args []string, force bool) (t
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
output := fmt.Sprintf("'%s', %dL %db written", filename, buf.LineCount(), bytes)
|
||||||
Lines: []string{fmt.Sprintf("'%s', %dL %db written", filename, buf.LineCount(), bytes)},
|
m.SetCommandOutput(output)
|
||||||
Inline: true,
|
|
||||||
})
|
|
||||||
buf.SetModified(false)
|
buf.SetModified(false)
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
// Command: Represents a command that can be executed from command mode.
|
// Command: Represents a command that can be executed from command mode.
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Name string // Full name: "quit"
|
Name string // Full name: "quit"
|
||||||
ShortForm string // Minimum abbreviation: "q"
|
ShortForm string // Minimum abbreviation: "q"
|
||||||
Handler func(m action.Model, args []string, force bool) tea.Cmd // Handler function
|
Handler func(m action.Model, args []string, force bool) tea.Cmd // Handler function
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,55 +162,6 @@ func (r *Registry) registerDefaults() {
|
|||||||
Handler: cmdRegisters,
|
Handler: cmdRegisters,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Buffer commands
|
|
||||||
r.Register(Command{
|
|
||||||
Name: "buffers",
|
|
||||||
ShortForm: "buffers",
|
|
||||||
Handler: cmdListBuffers,
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Register(Command{
|
|
||||||
Name: "ls",
|
|
||||||
ShortForm: "ls",
|
|
||||||
Handler: cmdListBuffers,
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Register(Command{
|
|
||||||
Name: "bn",
|
|
||||||
ShortForm: "bn",
|
|
||||||
Handler: cmdNextBuffer,
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Register(Command{
|
|
||||||
Name: "bp",
|
|
||||||
ShortForm: "bp",
|
|
||||||
Handler: cmdPrevBuffer,
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Register(Command{
|
|
||||||
Name: "bf",
|
|
||||||
ShortForm: "bf",
|
|
||||||
Handler: cmdFirstBuffer,
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Register(Command{
|
|
||||||
Name: "bl",
|
|
||||||
ShortForm: "bl",
|
|
||||||
Handler: cmdLastBuffer,
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Register(Command{
|
|
||||||
Name: "b",
|
|
||||||
ShortForm: "b",
|
|
||||||
Handler: cmdSelectBuffer,
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Register(Command{
|
|
||||||
Name: "bdelete",
|
|
||||||
ShortForm: "bd",
|
|
||||||
Handler: cmdDeleteBuffer,
|
|
||||||
})
|
|
||||||
|
|
||||||
// File commands
|
// File commands
|
||||||
r.Register(Command{
|
r.Register(Command{
|
||||||
Name: "edit",
|
Name: "edit",
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
const CommandOutputExitMessage = "Press ENTER to continue"
|
|
||||||
|
|
||||||
type CommandOutput struct {
|
|
||||||
Title string
|
|
||||||
Lines []string
|
|
||||||
ScrollOffset int // Not implemented yet
|
|
||||||
// Height is computing via lines and title
|
|
||||||
Inline bool // Show inline instead of the window
|
|
||||||
IsError bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandOutput.Height: Compute the height (in lines) based on the line count, and title.
|
|
||||||
func (c *CommandOutput) Height() int {
|
|
||||||
if c.Inline {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var h int
|
|
||||||
h += len(c.Lines)
|
|
||||||
if strings.TrimSpace(c.Title) != "" {
|
|
||||||
h++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Padding:
|
|
||||||
// +1 for 'enter key...' message
|
|
||||||
// +1 for top bar (border)
|
|
||||||
h += 2
|
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandOutput.IsActive: Is the command output in a state that should be displayed.
|
|
||||||
func (c *CommandOutput) IsActive() bool {
|
|
||||||
return len(c.Lines) > 0
|
|
||||||
}
|
|
||||||
@ -7,7 +7,6 @@ const (
|
|||||||
NormalMode Mode = iota
|
NormalMode Mode = iota
|
||||||
InsertMode
|
InsertMode
|
||||||
CommandMode
|
CommandMode
|
||||||
CommandOutputMode
|
|
||||||
VisualMode
|
VisualMode
|
||||||
VisualLineMode
|
VisualLineMode
|
||||||
VisualBlockMode
|
VisualBlockMode
|
||||||
|
|||||||
@ -11,18 +11,6 @@ const (
|
|||||||
BlockwiseRegister
|
BlockwiseRegister
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r RegisterType) ToString() string {
|
|
||||||
switch r {
|
|
||||||
case CharwiseRegister:
|
|
||||||
return "c"
|
|
||||||
case LinewiseRegister:
|
|
||||||
return "l"
|
|
||||||
case BlockwiseRegister:
|
|
||||||
return "b"
|
|
||||||
}
|
|
||||||
return "-"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register: Stores yanked or deleted text with metadata about how it should be
|
// Register: Stores yanked or deleted text with metadata about how it should be
|
||||||
// pasted. The Type determines paste behavior and Content holds the text lines.
|
// pasted. The Type determines paste behavior and Content holds the text lines.
|
||||||
type Register struct {
|
type Register struct {
|
||||||
|
|||||||
@ -302,8 +302,8 @@ func TestCommandModeErrors(t *testing.T) {
|
|||||||
sendKeys(tm, ":", "u", "n", "k", "n", "o", "w", "n", "c", "m", "d", "enter")
|
sendKeys(tm, ":", "u", "n", "k", "n", "o", "w", "n", "c", "m", "d", "enter")
|
||||||
|
|
||||||
m := getFinalModel(t, tm)
|
m := getFinalModel(t, tm)
|
||||||
if m.commandOutput == nil || !m.commandOutput.IsError {
|
if m.commandError == nil {
|
||||||
t.Error("expected commandOutput with IsError to be set for unknown command")
|
t.Error("expected commandError to be set for unknown command")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -317,8 +317,8 @@ func TestCommandModeErrors(t *testing.T) {
|
|||||||
sendKeys(tm, ":", "s", "e", "t", " ", "n", "u", "enter")
|
sendKeys(tm, ":", "s", "e", "t", " ", "n", "u", "enter")
|
||||||
|
|
||||||
m := getFinalModel(t, tm)
|
m := getFinalModel(t, tm)
|
||||||
if m.commandOutput != nil && m.commandOutput.IsError {
|
if m.commandError != nil {
|
||||||
t.Errorf("expected no error output, got %v", m.commandOutput.Lines)
|
t.Errorf("expected commandError to be nil, got %v", m.commandError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -332,8 +332,8 @@ func TestCommandModeErrors(t *testing.T) {
|
|||||||
sendKeys(tm, ":", "esc")
|
sendKeys(tm, ":", "esc")
|
||||||
|
|
||||||
m := getFinalModel(t, tm)
|
m := getFinalModel(t, tm)
|
||||||
if m.commandOutput != nil && m.commandOutput.IsError {
|
if m.commandError != nil {
|
||||||
t.Errorf("expected no error output after esc, got %v", m.commandOutput.Lines)
|
t.Errorf("expected commandError to be nil after esc, got %v", m.commandError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -345,8 +345,8 @@ func TestCommandEdit(t *testing.T) {
|
|||||||
sendKeys(tm, "enter")
|
sendKeys(tm, "enter")
|
||||||
|
|
||||||
m := getFinalModel(t, tm)
|
m := getFinalModel(t, tm)
|
||||||
if m.commandOutput == nil || !m.commandOutput.IsError {
|
if m.commandError == nil {
|
||||||
t.Error("expected commandOutput with IsError to be set for edit without args")
|
t.Error("expected commandError to be set for edit without args")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,8 @@ type Model struct {
|
|||||||
// Command line state
|
// Command line state
|
||||||
command string
|
command string
|
||||||
commandCursor int
|
commandCursor int
|
||||||
commandOutput *core.CommandOutput
|
commandError error
|
||||||
|
commandOutput string
|
||||||
|
|
||||||
// Global settings
|
// Global settings
|
||||||
settings core.EditorSettings
|
settings core.EditorSettings
|
||||||
@ -271,11 +272,19 @@ func (m *Model) SetCommandCursor(cur int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) CommandOutput() *core.CommandOutput {
|
func (m *Model) CommandError() error {
|
||||||
|
return m.commandError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) SetCommandError(err error) {
|
||||||
|
m.commandError = err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) CommandOutput() string {
|
||||||
return m.commandOutput
|
return m.commandOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetCommandOutput(out *core.CommandOutput) {
|
func (m *Model) SetCommandOutput(out string) {
|
||||||
m.commandOutput = out
|
m.commandOutput = out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,8 @@ func NewModelBuilder() *ModelBuilder {
|
|||||||
insertAction: nil,
|
insertAction: nil,
|
||||||
command: "",
|
command: "",
|
||||||
commandCursor: 0,
|
commandCursor: 0,
|
||||||
commandOutput: nil,
|
commandError: nil,
|
||||||
|
commandOutput: "",
|
||||||
settings: core.NewDefaultSettings(),
|
settings: core.NewDefaultSettings(),
|
||||||
registers: core.DefaultRegisters(),
|
registers: core.DefaultRegisters(),
|
||||||
styles: style.DefaultStyles(),
|
styles: style.DefaultStyles(),
|
||||||
@ -115,9 +116,15 @@ func (mb *ModelBuilder) WithCommandCursor(cursor int) *ModelBuilder {
|
|||||||
return mb
|
return mb
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModelBuilder.WithCommandOutput: Set the command line output.
|
// ModelBuilder.WithCommandError: Set the command line error state.
|
||||||
func (mb *ModelBuilder) WithCommandOutput(out *core.CommandOutput) *ModelBuilder {
|
func (mb *ModelBuilder) WithCommandError(err error) *ModelBuilder {
|
||||||
mb.model.commandOutput = out
|
mb.model.commandError = err
|
||||||
|
return mb
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelBuilder.WithCommandOutput: Set the command line output text.
|
||||||
|
func (mb *ModelBuilder) WithCommandOutput(output string) *ModelBuilder {
|
||||||
|
mb.model.commandOutput = output
|
||||||
return mb
|
return mb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package editor
|
package editor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,18 +54,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
if msg.Type == tea.KeyCtrlC {
|
if msg.Type == tea.KeyCtrlC {
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
|
cmd = m.input.Handle(m, msg.String())
|
||||||
// TODO: This is not great
|
|
||||||
// TODO: Any vim action should exit also
|
|
||||||
// Simple override for command output mode for now
|
|
||||||
if m.Mode() == core.CommandOutputMode {
|
|
||||||
if msg.Type == tea.KeyEnter {
|
|
||||||
m.SetMode(core.NormalMode)
|
|
||||||
m.SetCommandOutput(&core.CommandOutput{})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cmd = m.input.Handle(m, msg.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep cursor in view after any update
|
// Keep cursor in view after any update
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Model.View: Renders the complete editor view including buffer content, line
|
// Model.View: Renders the complete editor view including buffer content, line
|
||||||
@ -27,16 +26,7 @@ func (m Model) View() string {
|
|||||||
|
|
||||||
// Command bar is seperate
|
// Command bar is seperate
|
||||||
cmdBar := drawCommandBar(m)
|
cmdBar := drawCommandBar(m)
|
||||||
view += cmdBar
|
return view + cmdBar
|
||||||
|
|
||||||
// Handle command output, draw on top
|
|
||||||
// TODO: This is not idea, but it works for now
|
|
||||||
cmd := m.CommandOutput()
|
|
||||||
if cmd != nil && cmd.IsActive() && !cmd.Inline && cmd.Height() > 0 {
|
|
||||||
view = overlayCommandOutputWindow(view, cmd, styles, m.termWidth)
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// viewWindow: Renders a single window's content including line numbers and buffer text.
|
// viewWindow: Renders a single window's content including line numbers and buffer text.
|
||||||
@ -149,6 +139,44 @@ func drawGutter(w *core.Window, styles style.Styles, options core.WinOptions, cu
|
|||||||
}
|
}
|
||||||
|
|
||||||
return view.String()
|
return view.String()
|
||||||
|
|
||||||
|
// if m.Settings().Number || m.Settings().RelativeNumber {
|
||||||
|
// var (
|
||||||
|
// gutter string
|
||||||
|
// currentLine bool = false
|
||||||
|
// lineNumber int
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// if m.Settings().RelativeNumber {
|
||||||
|
// // Relative line numbers: show distance from cursor, current line shows absolute
|
||||||
|
// if i > win.Cursor.Line {
|
||||||
|
// lineNumber = i - win.Cursor.Line
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
||||||
|
// } else if i < win.Cursor.Line {
|
||||||
|
// lineNumber = win.Cursor.Line - i
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
||||||
|
// } else {
|
||||||
|
// // Current line: show absolute number if Number is also set, otherwise show 0
|
||||||
|
// currentLine = true
|
||||||
|
// if m.Settings().Number {
|
||||||
|
// lineNumber = i + 1
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
||||||
|
// } else {
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, 0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else if m.Settings().Number {
|
||||||
|
// // Absolute line numbers only
|
||||||
|
// lineNumber = i + 1
|
||||||
|
// currentLine = (i == win.Cursor.Line)
|
||||||
|
// gutter = fmt.Sprintf("%*d ", m.Settings().GutterSize-1, lineNumber)
|
||||||
|
// }
|
||||||
|
// if currentLine {
|
||||||
|
// view.WriteString(m.Styles().GutterCurrentLine.Render(gutter))
|
||||||
|
// } else {
|
||||||
|
// view.WriteString(m.Styles().Gutter.Render(gutter))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawStatusBar: Renders the status bar with mode and cursor position,
|
// drawStatusBar: Renders the status bar with mode and cursor position,
|
||||||
@ -157,7 +185,7 @@ func drawStatusBar(w *core.Window, mode core.Mode) string {
|
|||||||
left := leftBar(w, mode)
|
left := leftBar(w, mode)
|
||||||
right := rightBar(w, mode)
|
right := rightBar(w, mode)
|
||||||
|
|
||||||
diff := w.Width - (lipgloss.Width(left) + lipgloss.Width(right))
|
diff := w.Width - (len(left) + len(right))
|
||||||
|
|
||||||
// This happens when the terminal spawns
|
// This happens when the terminal spawns
|
||||||
if diff <= 0 {
|
if diff <= 0 {
|
||||||
@ -209,13 +237,13 @@ func drawCommandBar(m Model) string {
|
|||||||
var leftBar string
|
var leftBar string
|
||||||
if m.Mode() == core.CommandMode {
|
if m.Mode() == core.CommandMode {
|
||||||
leftBar = ":"
|
leftBar = ":"
|
||||||
cmd := []rune(m.Command())
|
cmd := m.Command()
|
||||||
cur := m.CommandCursor()
|
cur := m.CommandCursor()
|
||||||
for i, r := range cmd {
|
for i := 0; i < len(cmd); i++ {
|
||||||
if i == cur {
|
if i == cur {
|
||||||
leftBar += m.Styles().CursorStyle(m.Mode()).Render(string(r))
|
leftBar += m.Styles().CursorStyle(m.Mode()).Render(string(cmd[i]))
|
||||||
} else {
|
} else {
|
||||||
leftBar += string(r)
|
leftBar += string(cmd[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Cursor at end of command
|
// Cursor at end of command
|
||||||
@ -223,14 +251,10 @@ func drawCommandBar(m Model) string {
|
|||||||
leftBar += m.Styles().CursorStyle(m.Mode()).Render(" ")
|
leftBar += m.Styles().CursorStyle(m.Mode()).Render(" ")
|
||||||
}
|
}
|
||||||
// bar = fmt.Sprintf("%s %d", bar, cur)
|
// bar = fmt.Sprintf("%s %d", bar, cur)
|
||||||
} else if out := m.CommandOutput(); out != nil && len(out.Lines) > 0 && out.Inline {
|
} else if m.CommandError() != nil {
|
||||||
// TODO: This is not perfect, temporary
|
leftBar = m.Styles().CommandError.Render(m.CommandError().Error())
|
||||||
text := strings.Join(out.Lines, " ")
|
} else if strings.TrimSpace(m.CommandOutput()) != "" {
|
||||||
if out.IsError {
|
leftBar = m.CommandOutput()
|
||||||
leftBar = m.Styles().CommandError.Render(text)
|
|
||||||
} else {
|
|
||||||
leftBar = text
|
|
||||||
}
|
|
||||||
} else if strings.TrimSpace(m.Command()) != "" {
|
} else if strings.TrimSpace(m.Command()) != "" {
|
||||||
leftBar = fmt.Sprintf(":%s", m.Command())
|
leftBar = fmt.Sprintf(":%s", m.Command())
|
||||||
}
|
}
|
||||||
@ -243,9 +267,9 @@ func drawCommandBar(m Model) string {
|
|||||||
rightBar = fmt.Sprintf("%-*s", width, m.input.Pending())
|
rightBar = fmt.Sprintf("%-*s", width, m.input.Pending())
|
||||||
}
|
}
|
||||||
|
|
||||||
dif := m.termWidth - (lipgloss.Width(leftBar) + lipgloss.Width(rightBar))
|
dif := m.termWidth - (len(leftBar) + len(rightBar))
|
||||||
|
|
||||||
bar := leftBar + strings.Repeat(" ", max(0, dif)) + rightBar
|
bar := leftBar + strings.Repeat(" ", dif) + rightBar
|
||||||
return bar
|
return bar
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,38 +317,3 @@ func posInsideSelection(w *core.Window, mode core.Mode, col, line int) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// overlayCommandOutputWindow: Draw the overlay of the command output window. This will override
|
|
||||||
// (overlay) the displayed content, so it should be used only when needed.
|
|
||||||
func overlayCommandOutputWindow(view string, cmd *core.CommandOutput, styles style.Styles, termWidth int) string {
|
|
||||||
// Safety check
|
|
||||||
if cmd == nil {
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split the lines and get the last few
|
|
||||||
lines := strings.Split(view, "\n")
|
|
||||||
|
|
||||||
// Build the overlay
|
|
||||||
var overlay []string
|
|
||||||
overlay = append(overlay, styles.CommandOutputBorder.Render(strings.Repeat(" ", termWidth)))
|
|
||||||
|
|
||||||
if strings.TrimSpace(cmd.Title) != "" {
|
|
||||||
overlay = append(overlay, cmd.Title)
|
|
||||||
}
|
|
||||||
for _, l := range cmd.Lines {
|
|
||||||
overlay = append(overlay, strings.ReplaceAll(l, "\n", "\\n"))
|
|
||||||
}
|
|
||||||
overlay = append(overlay, styles.CommandContinueMessage.Render(core.CommandOutputExitMessage))
|
|
||||||
|
|
||||||
// NOTE: strings.Split on "\n" is safe as long as no style uses .Width()/.Height()/.Padding()/.Margin(),
|
|
||||||
// which would cause Lipgloss to embed newlines internally and corrupt the line count.
|
|
||||||
// If block-level styles are ever added, this approach must be replaced.
|
|
||||||
|
|
||||||
// Remove 'h' lines from back of view and append overlay
|
|
||||||
h := len(overlay)
|
|
||||||
final := lines[:max(0, len(lines)-h)]
|
|
||||||
final = append(final, overlay...)
|
|
||||||
|
|
||||||
return strings.Join(final, "\n")
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package operator
|
package operator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@ -23,11 +25,7 @@ func (o YankOperator) Operate(m action.Model, start, end core.Position, mtype co
|
|||||||
case core.NormalMode:
|
case core.NormalMode:
|
||||||
yankNormalMode(m, start, end, mtype)
|
yankNormalMode(m, start, end, mtype)
|
||||||
default:
|
default:
|
||||||
m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("'y' operator not yet implemented."))
|
||||||
Lines: []string{"'y' operator not yet implemented."},
|
|
||||||
Inline: true,
|
|
||||||
IsError: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
win.SetCursorCol(start.Col)
|
win.SetCursorCol(start.Col)
|
||||||
@ -67,14 +65,10 @@ func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionT
|
|||||||
switch {
|
switch {
|
||||||
case mtype.IsCharwise():
|
case mtype.IsCharwise():
|
||||||
// This shouldn't happen
|
// This shouldn't happen
|
||||||
// if start.Line != end.Line {
|
if start.Line != end.Line {
|
||||||
// m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("Start line and end line must match for charwise yank operations."))
|
||||||
// Lines: []string{"Start line and end line must match for charwise yank operations."},
|
return
|
||||||
// Inline: true,
|
}
|
||||||
// IsError: true,
|
|
||||||
// })
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
line := buf.Lines[start.Line]
|
line := buf.Lines[start.Line]
|
||||||
|
|
||||||
@ -93,14 +87,10 @@ func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionT
|
|||||||
|
|
||||||
case mtype == core.Linewise:
|
case mtype == core.Linewise:
|
||||||
// This shouldn't happen
|
// This shouldn't happen
|
||||||
// if start.Col != end.Col {
|
if start.Col != end.Col {
|
||||||
// m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("Start column and end column must match for linewise yank operations."))
|
||||||
// Lines: []string{"Start column and end column must match for linewise yank operations."},
|
return
|
||||||
// Inline: true,
|
}
|
||||||
// IsError: true,
|
|
||||||
// })
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// These don't need to be validated, they are validated before being passed into the function
|
// These don't need to be validated, they are validated before being passed into the function
|
||||||
startY := min(start.Line, end.Line)
|
startY := min(start.Line, end.Line)
|
||||||
@ -156,14 +146,10 @@ func yankVisualLineMode(m action.Model, start, end core.Position) {
|
|||||||
buf := m.ActiveBuffer()
|
buf := m.ActiveBuffer()
|
||||||
|
|
||||||
// This shouldn't happen
|
// This shouldn't happen
|
||||||
// if start.Col != end.Col {
|
if start.Col != end.Col {
|
||||||
// m.SetCommandOutput(&core.CommandOutput{
|
m.SetCommandError(fmt.Errorf("Start column and end column must match for linewise yank operations."))
|
||||||
// Lines: []string{"Start column and end column must match for linewise yank operations."},
|
return
|
||||||
// Inline: true,
|
}
|
||||||
// IsError: true,
|
|
||||||
// })
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// These don't need to be validated, they are validated before being passed into the function
|
// These don't need to be validated, they are validated before being passed into the function
|
||||||
startY := min(start.Line, end.Line)
|
startY := min(start.Line, end.Line)
|
||||||
|
|||||||
@ -25,9 +25,7 @@ type Styles struct {
|
|||||||
StatusBarActive lipgloss.Style
|
StatusBarActive lipgloss.Style
|
||||||
|
|
||||||
// Command line
|
// Command line
|
||||||
CommandError lipgloss.Style
|
CommandError lipgloss.Style
|
||||||
CommandOutputBorder lipgloss.Style
|
|
||||||
CommandContinueMessage lipgloss.Style
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultStyles returns the default editor color scheme.
|
// DefaultStyles returns the default editor color scheme.
|
||||||
@ -61,12 +59,6 @@ func DefaultStyles() Styles {
|
|||||||
|
|
||||||
CommandError: lipgloss.NewStyle().
|
CommandError: lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("#e3203a")),
|
Foreground(lipgloss.Color("#e3203a")),
|
||||||
|
|
||||||
CommandOutputBorder: lipgloss.NewStyle().
|
|
||||||
Background(lipgloss.Color("#000000")),
|
|
||||||
|
|
||||||
CommandContinueMessage: lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("#546fba")),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user