feat: implemented the command window! Not tested. Maybe we need some?
All checks were successful
Run Test Suite / test (push) Successful in 13s

This commit is contained in:
Hayden Hargreaves 2026-03-14 23:13:59 -07:00
parent edbed61949
commit 10e37b82af
18 changed files with 579 additions and 396 deletions

View File

@ -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
}

View File

@ -20,8 +20,7 @@ type mockModel struct {
insertKeys []string
command string
commandCursor int
commandError error
commandOutput string
commandOutput *core.CommandOutput
lastFind core.LastFindCommand
}
@ -96,10 +95,8 @@ 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) 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 }

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {
@ -217,27 +245,65 @@ func cmdEdit(m action.Model, args []string, force bool) tea.Cmd {
// cmdRegisters: Handles :register command (debug - displays register content).
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."))
return nil
// 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)
}
}
name := rune(args[0][0])
reg, found := m.GetRegister(name)
if !found {
m.SetCommandError(fmt.Errorf("Could not find register '%c'.", name))
return nil
}
content := strings.Join(reg.Content, "\\n")
t := reg.Type
m.SetCommandOutput(fmt.Sprintf("Type: %d Name: \"%c Content: %s", t, name, content))
m.SetMode(core.CommandOutputMode)
m.SetCommandOutput(&core.CommandOutput{
Title: ":reg",
Lines: lines,
Inline: false,
IsError: false,
})
return nil
}
@ -255,14 +321,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

View File

@ -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

39
internal/core/command.go Normal file
View 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
}

View File

@ -7,6 +7,7 @@ const (
NormalMode Mode = iota
InsertMode
CommandMode
CommandOutputMode
VisualMode
VisualLineMode
VisualBlockMode

View File

@ -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 {

View File

@ -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")
}
})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,6 +1,7 @@
package editor
import (
"git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea"
)
@ -54,8 +55,19 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg.Type == tea.KeyCtrlC {
return m, tea.Quit
}
// 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
win := m.ActiveWindow()

View File

@ -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")
}

View File

@ -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)

View File

@ -26,6 +26,8 @@ type Styles struct {
// Command line
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")),
}
}