Compare commits
3 Commits
edbed61949
...
6b8845b245
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b8845b245 | ||
|
|
d5ad3dcdcd | ||
|
|
10e37b82af |
@ -12,8 +12,7 @@ type ExitCommandMode struct{}
|
||||
func (a ExitCommandMode) Execute(m Model) tea.Cmd {
|
||||
m.SetCommandCursor(0)
|
||||
m.SetCommand("")
|
||||
m.SetCommandOutput("")
|
||||
m.SetCommandError(nil)
|
||||
m.SetCommandOutput(&core.CommandOutput{})
|
||||
m.SetMode(core.NormalMode)
|
||||
return nil
|
||||
}
|
||||
@ -128,7 +127,6 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Clear command state and return to normal mode
|
||||
m.SetCommandCursor(0)
|
||||
m.SetCommandError(nil)
|
||||
m.SetMode(core.NormalMode)
|
||||
|
||||
if a.Registry == nil || cmdLine == "" {
|
||||
@ -137,7 +135,12 @@ func (a CommandExecute) Execute(m Model) tea.Cmd {
|
||||
|
||||
cmd, err := a.Registry.Execute(m, cmdLine)
|
||||
if err != nil {
|
||||
m.SetCommandError(err)
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{err.Error()},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -20,8 +20,7 @@ type mockModel struct {
|
||||
insertKeys []string
|
||||
command string
|
||||
commandCursor int
|
||||
commandError error
|
||||
commandOutput string
|
||||
commandOutput *core.CommandOutput
|
||||
lastFind core.LastFindCommand
|
||||
}
|
||||
|
||||
@ -92,14 +91,12 @@ func (m *mockModel) SetLastFind(char string, forward, inclusive bool) {
|
||||
func (m *mockModel) GetLastFind() *core.LastFindCommand { return &m.lastFind }
|
||||
|
||||
// Command Mode State
|
||||
func (m *mockModel) Command() string { return m.command }
|
||||
func (m *mockModel) SetCommand(cmd string) { m.command = cmd }
|
||||
func (m *mockModel) CommandCursor() int { return m.commandCursor }
|
||||
func (m *mockModel) SetCommandCursor(cur int) { m.commandCursor = cur }
|
||||
func (m *mockModel) CommandError() error { return m.commandError }
|
||||
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 }
|
||||
func (m *mockModel) Command() string { return m.command }
|
||||
func (m *mockModel) SetCommand(cmd string) { m.command = cmd }
|
||||
func (m *mockModel) CommandCursor() int { return m.commandCursor }
|
||||
func (m *mockModel) SetCommandCursor(cur int) { m.commandCursor = cur }
|
||||
func (m *mockModel) CommandOutput() *core.CommandOutput { return m.commandOutput }
|
||||
func (m *mockModel) SetCommandOutput(out *core.CommandOutput) { m.commandOutput = out }
|
||||
|
||||
// Editor-wide State
|
||||
func (m *mockModel) Mode() core.Mode { return m.mode }
|
||||
|
||||
@ -37,10 +37,8 @@ type Model interface {
|
||||
SetCommand(cmd string)
|
||||
CommandCursor() int
|
||||
SetCommandCursor(cur int)
|
||||
CommandError() error
|
||||
SetCommandError(err error)
|
||||
CommandOutput() string
|
||||
SetCommandOutput(out string)
|
||||
CommandOutput() *core.CommandOutput
|
||||
SetCommandOutput(out *core.CommandOutput)
|
||||
|
||||
// ==================================================
|
||||
// Editor-wide State
|
||||
|
||||
@ -20,8 +20,7 @@ type EnterComandMode struct{}
|
||||
func (a EnterComandMode) Execute(m Model) tea.Cmd {
|
||||
m.SetMode(core.CommandMode)
|
||||
m.SetCommand("")
|
||||
m.SetCommandOutput("")
|
||||
m.SetCommandError(nil)
|
||||
m.SetCommandOutput(&core.CommandOutput{})
|
||||
m.SetCommandCursor(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
@ -21,7 +20,12 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
// Get reg
|
||||
reg, found := m.GetRegister('"')
|
||||
if !found {
|
||||
m.SetCommandError(fmt.Errorf("\"\" register is broken. Uh oh."))
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{"\"\" register is broken. Uh oh."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -55,7 +59,12 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Shouldn't happen, just a check
|
||||
if len(lines) != 1 {
|
||||
m.SetCommandError(fmt.Errorf("Charwise register should only have a single line of content."))
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{"Charwise register should only have a single line of content."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
break
|
||||
}
|
||||
|
||||
@ -73,7 +82,12 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
win.SetCursorCol(x + len(cnt))
|
||||
}
|
||||
default:
|
||||
m.SetCommandError(fmt.Errorf("core.Register type is not implemented."))
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{"core.Register type is not implemented."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -100,7 +114,12 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
// Get reg
|
||||
reg, found := m.GetRegister('"')
|
||||
if !found {
|
||||
m.SetCommandError(fmt.Errorf("\"\" register is broken. Uh oh."))
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{"\"\" register is broken. Uh oh."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -125,7 +144,12 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Shouldn't happen, just a check
|
||||
if len(lines) != 1 {
|
||||
m.SetCommandError(fmt.Errorf("Charwise register should only have a single line of content."))
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{"Charwise register should only have a single line of content."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
break
|
||||
}
|
||||
|
||||
@ -143,7 +167,12 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
win.SetCursorCol(x + len(cnt))
|
||||
}
|
||||
default:
|
||||
m.SetCommandError(fmt.Errorf("core.Register type is not implemented."))
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{"core.Register type is not implemented."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -167,7 +196,12 @@ func (a VisualPaste) Execute(m Model) tea.Cmd {
|
||||
// Get register content to paste
|
||||
reg, found := m.GetRegister('"')
|
||||
if !found {
|
||||
m.SetCommandError(fmt.Errorf("\"\" register is broken. Uh oh."))
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{"\"\" register is broken. Uh oh."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,11 @@ func cmdQuit(m action.Model, args []string, force bool) tea.Cmd {
|
||||
// Cannot exit if any buffer has unsaved changes
|
||||
for _, buf := range bufs {
|
||||
if buf.Modified {
|
||||
m.SetCommandError(fmt.Errorf("unsaved changes to '%s'", buf.Filename))
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{fmt.Sprintf("unsaved changes to '%s'", buf.Filename)},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
m.ActiveWindow().SetBuffer(buf)
|
||||
return nil
|
||||
}
|
||||
@ -62,7 +66,11 @@ func cmdWrite(m action.Model, args []string, force bool) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
cmd, err := writeBuffer(m, buf, args, force)
|
||||
if err != nil {
|
||||
m.SetCommandError(err)
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{err.Error()},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@ -76,7 +84,11 @@ func cmdWriteAll(m action.Model, args []string, force bool) tea.Cmd {
|
||||
if buf.Modified {
|
||||
cmd, err := writeBuffer(m, buf, args, force)
|
||||
if err != nil {
|
||||
m.SetCommandError(err)
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{err.Error()},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
cmds = append(cmds, cmd)
|
||||
@ -91,7 +103,11 @@ func cmdWriteQuit(m action.Model, args []string, force bool) tea.Cmd {
|
||||
buf := m.ActiveBuffer()
|
||||
cmd, err := writeBuffer(m, buf, args, force)
|
||||
if err != nil {
|
||||
m.SetCommandError(err)
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{err.Error()},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -108,7 +124,11 @@ func cmdWriteQuitAll(m action.Model, args []string, force bool) tea.Cmd {
|
||||
if buf.Modified {
|
||||
cmd, err := writeBuffer(m, buf, args, force)
|
||||
if err != nil {
|
||||
m.SetCommandError(err)
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{err.Error()},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
cmds = append(cmds, cmd)
|
||||
@ -123,7 +143,11 @@ func cmdWriteQuitAll(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
|
||||
if len(args) < 1 {
|
||||
m.SetCommandError(fmt.Errorf(":edit requires an argument"))
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{":edit requires an argument"},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -156,7 +180,11 @@ func cmdEdit(m action.Model, args []string, force bool) tea.Cmd {
|
||||
notFound := errors.Is(err, os.ErrNotExist)
|
||||
|
||||
if err != nil && !notFound {
|
||||
m.SetCommandError(err)
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{err.Error()},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
if file != nil {
|
||||
@ -215,29 +243,427 @@ func cmdEdit(m action.Model, args []string, force bool) tea.Cmd {
|
||||
// Register Commands
|
||||
// --------------------------------------------------
|
||||
|
||||
// cmdRegisters: Handles :register command (debug - displays register content).
|
||||
// cmdRegisters: Handles :register command
|
||||
func cmdRegisters(m action.Model, args []string, force bool) tea.Cmd {
|
||||
// TODO: This is temporary, for debugging
|
||||
if len(args) < 1 {
|
||||
m.SetCommandError(fmt.Errorf("Please provide a name. Register dump not yet implemented."))
|
||||
regs := m.Registers()
|
||||
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
|
||||
}
|
||||
|
||||
if len(args[0]) != 1 {
|
||||
m.SetCommandError(fmt.Errorf("Name should be a single character."))
|
||||
// BUG: We can actually handle many now
|
||||
// if len(args[0]) != 1 {
|
||||
// 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
|
||||
}
|
||||
|
||||
name := rune(args[0][0])
|
||||
reg, found := m.GetRegister(name)
|
||||
if !found {
|
||||
m.SetCommandError(fmt.Errorf("Could not find register '%c'.", name))
|
||||
if len(args) > 1 {
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{fmt.Sprintf("Trailing characters: %s", strings.Join(args[1:], " "))},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
content := strings.Join(reg.Content, "\\n")
|
||||
t := reg.Type
|
||||
m.SetCommandOutput(fmt.Sprintf("Type: %d Name: \"%c Content: %s", t, name, content))
|
||||
bufs := m.Buffers()
|
||||
|
||||
// If we can parse the input as number, try an ID
|
||||
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
|
||||
}
|
||||
|
||||
@ -255,14 +681,20 @@ func cmdRegisters(m action.Model, args []string, force bool) tea.Cmd {
|
||||
// :set ts=4 - set tabstop to 4 (abbreviation)
|
||||
func cmdSet(m action.Model, args []string, force bool) tea.Cmd {
|
||||
if len(args) == 0 {
|
||||
out := fmt.Sprintf("%+v", m.Settings())
|
||||
m.SetCommandOutput(out)
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{fmt.Sprintf("%+v", m.Settings())},
|
||||
Inline: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
if err := parseSetOption(m, arg); err != nil {
|
||||
m.SetCommandError(err)
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{err.Error()},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -75,8 +75,10 @@ func writeBuffer(m action.Model, buf *core.Buffer, args []string, force bool) (t
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output := fmt.Sprintf("'%s', %dL %db written", filename, buf.LineCount(), bytes)
|
||||
m.SetCommandOutput(output)
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{fmt.Sprintf("'%s', %dL %db written", filename, buf.LineCount(), bytes)},
|
||||
Inline: true,
|
||||
})
|
||||
buf.SetModified(false)
|
||||
|
||||
return nil, nil
|
||||
|
||||
@ -10,8 +10,8 @@ import (
|
||||
|
||||
// Command: Represents a command that can be executed from command mode.
|
||||
type Command struct {
|
||||
Name string // Full name: "quit"
|
||||
ShortForm string // Minimum abbreviation: "q"
|
||||
Name string // Full name: "quit"
|
||||
ShortForm string // Minimum abbreviation: "q"
|
||||
Handler func(m action.Model, args []string, force bool) tea.Cmd // Handler function
|
||||
}
|
||||
|
||||
@ -162,6 +162,55 @@ func (r *Registry) registerDefaults() {
|
||||
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
|
||||
r.Register(Command{
|
||||
Name: "edit",
|
||||
|
||||
39
internal/core/command.go
Normal file
39
internal/core/command.go
Normal file
@ -0,0 +1,39 @@
|
||||
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,6 +7,7 @@ const (
|
||||
NormalMode Mode = iota
|
||||
InsertMode
|
||||
CommandMode
|
||||
CommandOutputMode
|
||||
VisualMode
|
||||
VisualLineMode
|
||||
VisualBlockMode
|
||||
|
||||
@ -11,6 +11,18 @@ const (
|
||||
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
|
||||
// pasted. The Type determines paste behavior and Content holds the text lines.
|
||||
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")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.commandError == nil {
|
||||
t.Error("expected commandError to be set for unknown command")
|
||||
if m.commandOutput == nil || !m.commandOutput.IsError {
|
||||
t.Error("expected commandOutput with IsError to be set for unknown command")
|
||||
}
|
||||
})
|
||||
|
||||
@ -317,8 +317,8 @@ func TestCommandModeErrors(t *testing.T) {
|
||||
sendKeys(tm, ":", "s", "e", "t", " ", "n", "u", "enter")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.commandError != nil {
|
||||
t.Errorf("expected commandError to be nil, got %v", m.commandError)
|
||||
if m.commandOutput != nil && m.commandOutput.IsError {
|
||||
t.Errorf("expected no error output, got %v", m.commandOutput.Lines)
|
||||
}
|
||||
})
|
||||
|
||||
@ -332,8 +332,8 @@ func TestCommandModeErrors(t *testing.T) {
|
||||
sendKeys(tm, ":", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.commandError != nil {
|
||||
t.Errorf("expected commandError to be nil after esc, got %v", m.commandError)
|
||||
if m.commandOutput != nil && m.commandOutput.IsError {
|
||||
t.Errorf("expected no error output after esc, got %v", m.commandOutput.Lines)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -345,8 +345,8 @@ func TestCommandEdit(t *testing.T) {
|
||||
sendKeys(tm, "enter")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.commandError == nil {
|
||||
t.Error("expected commandError to be set for edit without args")
|
||||
if m.commandOutput == nil || !m.commandOutput.IsError {
|
||||
t.Error("expected commandOutput with IsError to be set for edit without args")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -39,8 +39,7 @@ type Model struct {
|
||||
// Command line state
|
||||
command string
|
||||
commandCursor int
|
||||
commandError error
|
||||
commandOutput string
|
||||
commandOutput *core.CommandOutput
|
||||
|
||||
// Global settings
|
||||
settings core.EditorSettings
|
||||
@ -272,19 +271,11 @@ func (m *Model) SetCommandCursor(cur int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) CommandError() error {
|
||||
return m.commandError
|
||||
}
|
||||
|
||||
func (m *Model) SetCommandError(err error) {
|
||||
m.commandError = err
|
||||
}
|
||||
|
||||
func (m *Model) CommandOutput() string {
|
||||
func (m *Model) CommandOutput() *core.CommandOutput {
|
||||
return m.commandOutput
|
||||
}
|
||||
|
||||
func (m *Model) SetCommandOutput(out string) {
|
||||
func (m *Model) SetCommandOutput(out *core.CommandOutput) {
|
||||
m.commandOutput = out
|
||||
}
|
||||
|
||||
|
||||
@ -25,8 +25,7 @@ func NewModelBuilder() *ModelBuilder {
|
||||
insertAction: nil,
|
||||
command: "",
|
||||
commandCursor: 0,
|
||||
commandError: nil,
|
||||
commandOutput: "",
|
||||
commandOutput: nil,
|
||||
settings: core.NewDefaultSettings(),
|
||||
registers: core.DefaultRegisters(),
|
||||
styles: style.DefaultStyles(),
|
||||
@ -116,15 +115,9 @@ func (mb *ModelBuilder) WithCommandCursor(cursor int) *ModelBuilder {
|
||||
return mb
|
||||
}
|
||||
|
||||
// ModelBuilder.WithCommandError: Set the command line error state.
|
||||
func (mb *ModelBuilder) WithCommandError(err error) *ModelBuilder {
|
||||
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
|
||||
// ModelBuilder.WithCommandOutput: Set the command line output.
|
||||
func (mb *ModelBuilder) WithCommandOutput(out *core.CommandOutput) *ModelBuilder {
|
||||
mb.model.commandOutput = out
|
||||
return mb
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package editor
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
@ -54,7 +55,18 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if msg.Type == tea.KeyCtrlC {
|
||||
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
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// Model.View: Renders the complete editor view including buffer content, line
|
||||
@ -26,7 +27,16 @@ func (m Model) View() string {
|
||||
|
||||
// Command bar is seperate
|
||||
cmdBar := drawCommandBar(m)
|
||||
return view + cmdBar
|
||||
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.
|
||||
@ -139,44 +149,6 @@ func drawGutter(w *core.Window, styles style.Styles, options core.WinOptions, cu
|
||||
}
|
||||
|
||||
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,
|
||||
@ -185,7 +157,7 @@ func drawStatusBar(w *core.Window, mode core.Mode) string {
|
||||
left := leftBar(w, mode)
|
||||
right := rightBar(w, mode)
|
||||
|
||||
diff := w.Width - (len(left) + len(right))
|
||||
diff := w.Width - (lipgloss.Width(left) + lipgloss.Width(right))
|
||||
|
||||
// This happens when the terminal spawns
|
||||
if diff <= 0 {
|
||||
@ -237,13 +209,13 @@ func drawCommandBar(m Model) string {
|
||||
var leftBar string
|
||||
if m.Mode() == core.CommandMode {
|
||||
leftBar = ":"
|
||||
cmd := m.Command()
|
||||
cmd := []rune(m.Command())
|
||||
cur := m.CommandCursor()
|
||||
for i := 0; i < len(cmd); i++ {
|
||||
for i, r := range cmd {
|
||||
if i == cur {
|
||||
leftBar += m.Styles().CursorStyle(m.Mode()).Render(string(cmd[i]))
|
||||
leftBar += m.Styles().CursorStyle(m.Mode()).Render(string(r))
|
||||
} else {
|
||||
leftBar += string(cmd[i])
|
||||
leftBar += string(r)
|
||||
}
|
||||
}
|
||||
// Cursor at end of command
|
||||
@ -251,10 +223,14 @@ func drawCommandBar(m Model) string {
|
||||
leftBar += m.Styles().CursorStyle(m.Mode()).Render(" ")
|
||||
}
|
||||
// bar = fmt.Sprintf("%s %d", bar, cur)
|
||||
} else if m.CommandError() != nil {
|
||||
leftBar = m.Styles().CommandError.Render(m.CommandError().Error())
|
||||
} else if strings.TrimSpace(m.CommandOutput()) != "" {
|
||||
leftBar = m.CommandOutput()
|
||||
} else if out := m.CommandOutput(); out != nil && len(out.Lines) > 0 && out.Inline {
|
||||
// TODO: This is not perfect, temporary
|
||||
text := strings.Join(out.Lines, " ")
|
||||
if out.IsError {
|
||||
leftBar = m.Styles().CommandError.Render(text)
|
||||
} else {
|
||||
leftBar = text
|
||||
}
|
||||
} else if strings.TrimSpace(m.Command()) != "" {
|
||||
leftBar = fmt.Sprintf(":%s", m.Command())
|
||||
}
|
||||
@ -267,9 +243,9 @@ func drawCommandBar(m Model) string {
|
||||
rightBar = fmt.Sprintf("%-*s", width, m.input.Pending())
|
||||
}
|
||||
|
||||
dif := m.termWidth - (len(leftBar) + len(rightBar))
|
||||
dif := m.termWidth - (lipgloss.Width(leftBar) + lipgloss.Width(rightBar))
|
||||
|
||||
bar := leftBar + strings.Repeat(" ", dif) + rightBar
|
||||
bar := leftBar + strings.Repeat(" ", max(0, dif)) + rightBar
|
||||
return bar
|
||||
}
|
||||
|
||||
@ -317,3 +293,38 @@ func posInsideSelection(w *core.Window, mode core.Mode, col, line int) bool {
|
||||
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,8 +1,6 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
@ -25,7 +23,11 @@ func (o YankOperator) Operate(m action.Model, start, end core.Position, mtype co
|
||||
case core.NormalMode:
|
||||
yankNormalMode(m, start, end, mtype)
|
||||
default:
|
||||
m.SetCommandError(fmt.Errorf("'y' operator not yet implemented."))
|
||||
m.SetCommandOutput(&core.CommandOutput{
|
||||
Lines: []string{"'y' operator not yet implemented."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
})
|
||||
}
|
||||
|
||||
win.SetCursorCol(start.Col)
|
||||
@ -65,10 +67,14 @@ func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionT
|
||||
switch {
|
||||
case mtype.IsCharwise():
|
||||
// This shouldn't happen
|
||||
if start.Line != end.Line {
|
||||
m.SetCommandError(fmt.Errorf("Start line and end line must match for charwise yank operations."))
|
||||
return
|
||||
}
|
||||
// if start.Line != end.Line {
|
||||
// m.SetCommandOutput(&core.CommandOutput{
|
||||
// Lines: []string{"Start line and end line must match for charwise yank operations."},
|
||||
// Inline: true,
|
||||
// IsError: true,
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
line := buf.Lines[start.Line]
|
||||
|
||||
@ -87,10 +93,14 @@ func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionT
|
||||
|
||||
case mtype == core.Linewise:
|
||||
// This shouldn't happen
|
||||
if start.Col != end.Col {
|
||||
m.SetCommandError(fmt.Errorf("Start column and end column must match for linewise yank operations."))
|
||||
return
|
||||
}
|
||||
// if start.Col != end.Col {
|
||||
// m.SetCommandOutput(&core.CommandOutput{
|
||||
// Lines: []string{"Start column and end column must match for linewise yank operations."},
|
||||
// Inline: true,
|
||||
// IsError: true,
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
// These don't need to be validated, they are validated before being passed into the function
|
||||
startY := min(start.Line, end.Line)
|
||||
@ -146,10 +156,14 @@ func yankVisualLineMode(m action.Model, start, end core.Position) {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
// This shouldn't happen
|
||||
if start.Col != end.Col {
|
||||
m.SetCommandError(fmt.Errorf("Start column and end column must match for linewise yank operations."))
|
||||
return
|
||||
}
|
||||
// if start.Col != end.Col {
|
||||
// m.SetCommandOutput(&core.CommandOutput{
|
||||
// Lines: []string{"Start column and end column must match for linewise yank operations."},
|
||||
// Inline: true,
|
||||
// IsError: true,
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
// These don't need to be validated, they are validated before being passed into the function
|
||||
startY := min(start.Line, end.Line)
|
||||
|
||||
@ -25,7 +25,9 @@ type Styles struct {
|
||||
StatusBarActive lipgloss.Style
|
||||
|
||||
// Command line
|
||||
CommandError lipgloss.Style
|
||||
CommandError lipgloss.Style
|
||||
CommandOutputBorder lipgloss.Style
|
||||
CommandContinueMessage lipgloss.Style
|
||||
}
|
||||
|
||||
// DefaultStyles returns the default editor color scheme.
|
||||
@ -59,6 +61,12 @@ func DefaultStyles() Styles {
|
||||
|
||||
CommandError: lipgloss.NewStyle().
|
||||
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