diff --git a/commands.txt b/commands.txt index f02a94a..a17df6b 100644 --- a/commands.txt +++ b/commands.txt @@ -59,6 +59,7 @@ Command categories: * ) : Move cursor forward by sentence. (MoveBySentence) * ( : Move cursor backward by sentence. (MoveBackwardBySentence) * } : Move cursor forward by paragraph. (MoveByParagraph) + (Note: Proper vi respects nroff/troff directives, but levi doesn't.) * { : Move cursor backward by paragraph. (MoveBackwardByParagraph) (Note: Proper vi respects nroff/troff directives, but levi doesn't.) * ]] : Move cursor forward by section. (MoveBySection) diff --git a/internal/editor/cmd.go b/internal/editor/cmd.go index e559a95..2280533 100644 --- a/internal/editor/cmd.go +++ b/internal/editor/cmd.go @@ -2,6 +2,23 @@ package editor type CmdKind int +type Loc struct { + Row int + Col int +} + +type Cmd struct { + Kind CmdKind + Num int + Letter rune + Pat string + Reg rune + Start Loc + End Loc + StartRow int + EndRow int +} + const ( CmdInvalid CmdKind = iota @@ -97,10 +114,10 @@ const ( CmdOpDelete CmdOpDeleteBefore CmdOpDeleteLine - CmdOpDelteRegion + CmdOpDeleteRegion CmdOpDeleteLineRegion CmdOpDeleteWord - CmdOpDelteToEnd + CmdOpDeleteToEnd CmdOpChangeLine CmdOpChangeRegion @@ -142,8 +159,3 @@ const ( CmdPromptQuitAll CmdPromptForceQuitAll ) - -type Cmd struct { - Kind CmdKind - Num int -} diff --git a/internal/editor/draw.go b/internal/editor/draw.go new file mode 100644 index 0000000..1b2b288 --- /dev/null +++ b/internal/editor/draw.go @@ -0,0 +1,110 @@ +package editor + +import ( + "unicode/utf8" + + "tea.kareha.org/lab/termi" +) + +func (ed *Editor) LineHeight(line string) int { + rc := utf8.RuneCountInString(line) + width := termi.StringWidth(line, rc) + return 1 + max(width-1, 0)/ed.w +} + +func (ed *Editor) DrawBuffer() { + y := 0 + for i := ed.vrow; i < len(ed.lines); i++ { + line := ed.Line(i) + + termi.MoveCursor(0, y) + termi.Draw(line) + + y += ed.LineHeight(line) + if y >= ed.h-1 { + break + } + } + + for ; y < ed.h-1; y++ { + termi.MoveCursor(0, y) + termi.Draw("~") + } +} + +func (ed *Editor) DrawStatus() { + var m string + switch ed.mode { + case ModeCommand: + m = "vi command" + case ModeInsert: + m = "vi insert" + case ModeSearch: + m = "vi search" + case ModePrompt: + m = "vi prompt" + default: + panic("invalid mode") + } + + termi.MoveCursor(0, ed.h-1) + if ed.message != "" { + termi.EnableInvert() + termi.Print(ed.message) + termi.DisableInvert() + ed.message = "" + } else { + termi.Printf("[%s] %s %d,%d %s", ed.parser.Cache(), m, ed.row, ed.col, ed.path) + } +} + +func (ed *Editor) UpdateCursor() { + // XXX approximation + width := termi.StringWidth(ed.CurrentLine(), ed.col) + ed.x = width % ed.w + dy := width / ed.w + + if ed.row < ed.vrow { + ed.vrow = ed.row + } + + y := 0 + for i := ed.vrow; i < ed.row; i++ { + y += ed.LineHeight(ed.lines[i]) + } + ed.y = y + dy + + for ed.y >= ed.h-1 { + ed.vrow++ + + y := 0 + for i := ed.vrow; i < ed.row; i++ { + y += ed.LineHeight(ed.lines[i]) + } + ed.y = y + dy + } +} + +func (ed *Editor) Repaint() { + w, h := termi.Size() + ed.w = w + ed.h = h + + termi.HideCursor() + + termi.Clear() + termi.HomeCursor() + + ed.UpdateCursor() + + ed.DrawBuffer() + ed.DrawStatus() + + termi.MoveCursor(ed.x, ed.y) + + termi.ShowCursor() +} + +func (ed *Editor) Draw() { + ed.Repaint() +} diff --git a/internal/editor/edit.go b/internal/editor/edit.go new file mode 100644 index 0000000..ecb7fb6 --- /dev/null +++ b/internal/editor/edit.go @@ -0,0 +1,41 @@ +package editor + +////////////////////// +// Editing Commands // +////////////////////// + +// r : Replace single character under cursor. +func (ed *Editor) EditReplace(letter rune, n int) { + ed.EnsureCommand() + ed.Unimplemented("EditReplace") +} + +// J : Join current line with next line. +func (ed *Editor) EditJoin(n int) { + ed.EnsureCommand() + ed.Unimplemented("EditJoin") +} + +// >> : Indent current line. +func (ed *Editor) EditIndent(n int) { + ed.EnsureCommand() + ed.Unimplemented("EditIndent") +} + +// << : Outdent current line. +func (ed *Editor) EditOutdent(n int) { + ed.EnsureCommand() + ed.Unimplemented("EditOutdent") +} + +// > : Indent region from current cursor to destination of motion . +func (ed *Editor) EditIndentRegion(start Loc, end Loc) { + ed.EnsureCommand() + ed.Unimplemented("EditIndentRegion") +} + +// < : Outdent region from current cursor to destination of motion . +func (ed *Editor) EditOutdentRegion(start Loc, end Loc) { + ed.EnsureCommand() + ed.Unimplemented("EditOutdentRegion") +} diff --git a/internal/editor/editor.go b/internal/editor/editor.go index 45ec5cb..362cd5a 100644 --- a/internal/editor/editor.go +++ b/internal/editor/editor.go @@ -146,6 +146,24 @@ func (ed *Editor) InsertRune(r rune) { ed.col = ed.inp.Column() } +func (ed *Editor) EnsureCommand() { + if ed.mode == ModeCommand { + return + } + + if ed.mode == ModeInsert { + ed.lines[ed.row] = ed.inp.Line() + ed.inp.Reset() + ed.mode = ModeCommand + ed.MoveLeft(1) + return + } +} + func (ed *Editor) Ring(message string) { ed.message = message } + +func (ed *Editor) Unimplemented(name string) { + ed.Ring("not implemented (" + name + ")") +} diff --git a/internal/editor/find.go b/internal/editor/find.go new file mode 100644 index 0000000..c1097c9 --- /dev/null +++ b/internal/editor/find.go @@ -0,0 +1,41 @@ +package editor + +//////////////////////////////// +// Character Finding Commands // +//////////////////////////////// + +// f : Find character forward in current line. +func (ed *Editor) FindForward(letter rune, n int) { + ed.EnsureCommand() + ed.Unimplemented("FindForward") +} + +// F : Find character backward in current line. +func (ed *Editor) FindBackward(letter rune, n int) { + ed.EnsureCommand() + ed.Unimplemented("FindBackward") +} + +// t : Find before character forward in current line. +func (ed *Editor) FindBeforeForward(letter rune, n int) { + ed.EnsureCommand() + ed.Unimplemented("FindBeforeForward") +} + +// T : Find before character backward in current line. +func (ed *Editor) FindBeforeBackward(letter rune, n int) { + ed.EnsureCommand() + ed.Unimplemented("FindBeforeBackward") +} + +// ; : Find next match. +func (ed *Editor) FindNextMatch(n int) { + ed.EnsureCommand() + ed.Unimplemented("FindNextMatch") +} + +// , : Find previous match. +func (ed *Editor) FindPrevMatch(n int) { + ed.EnsureCommand() + ed.Unimplemented("FindPrevMatch") +} diff --git a/internal/editor/insert.go b/internal/editor/insert.go index aa976b4..79e1b36 100644 --- a/internal/editor/insert.go +++ b/internal/editor/insert.go @@ -1,19 +1,23 @@ package editor +//////////////////////// +// Insertion Commands // +//////////////////////// + +// +// Enter Insert Mode +// + // i : Switch to insert mode before cursor. func (ed *Editor) InsertBefore(n int) { - if ed.mode == ModeInsert { - panic("invalid state") - } + ed.EnsureCommand() ed.inp.Init(ed.CurrentLine(), ed.col) ed.mode = ModeInsert } // a : Switch to insert mode after cursor. func (ed *Editor) InsertAfter(n int) { - if ed.mode == ModeInsert { - panic("invalid state") - } + ed.EnsureCommand() rc := ed.RuneCount() if ed.col >= rc-1 { ed.col = rc @@ -22,3 +26,37 @@ func (ed *Editor) InsertAfter(n int) { } ed.InsertBefore(n) } + +// I : Switch to insert mode before first non-blank character of current line. +func (ed *Editor) InsertBeforeNonBlank(n int) { + ed.EnsureCommand() + ed.Unimplemented("InsertBeforeNonBlank") +} + +// A : Switch to insert mode after end of current line. +func (ed *Editor) InsertAfterEnd(n int) { + ed.EnsureCommand() + ed.Unimplemented("InsertAfterEnd") +} + +// R : Switch to replace (overwrite) mode. +func (ed *Editor) InsertOverwrite(n int) { + ed.EnsureCommand() + ed.Unimplemented("InsertOverwrite") +} + +// +// Open Line +// + +// o : Open a new line below and switch to insert mode. +func (ed *Editor) InsertOpenBelow(n int) { + ed.EnsureCommand() + ed.Unimplemented("InsertOpenBelow") +} + +// O : Open a new line above and switch to insert mode. +func (ed *Editor) InsertOpenAbove(n int) { + ed.EnsureCommand() + ed.Unimplemented("InsertOpenAbove") +} diff --git a/internal/editor/main.go b/internal/editor/main.go index 807a054..d10189f 100644 --- a/internal/editor/main.go +++ b/internal/editor/main.go @@ -4,16 +4,6 @@ import ( "tea.kareha.org/lab/termi" ) -func (ed *Editor) ExitInsert() { - if ed.mode != ModeInsert { - panic("invalid state") - } - ed.lines[ed.row] = ed.inp.Line() - ed.inp.Reset() - ed.mode = ModeCommand - ed.MoveLeft(1) -} - func (ed *Editor) InsertNewline() { if ed.mode != ModeInsert { panic("invalid state") @@ -85,7 +75,7 @@ func (ed *Editor) Main() { case termi.KeyRune: switch key.Rune { case termi.RuneEscape: - ed.ExitInsert() + ed.EnsureCommand() case termi.RuneEnter: ed.InsertNewline() case termi.RuneBackspace: @@ -96,16 +86,12 @@ func (ed *Editor) Main() { ed.InsertRune(key.Rune) } case termi.KeyUp: - ed.ExitInsert() ed.MoveUp(1) case termi.KeyDown: - ed.ExitInsert() ed.MoveDown(1) case termi.KeyRight: - ed.ExitInsert() ed.MoveRight(1) case termi.KeyLeft: - ed.ExitInsert() ed.MoveLeft(1) default: ed.Ring("unknown key") diff --git a/internal/editor/mark.go b/internal/editor/mark.go new file mode 100644 index 0000000..efbedaf --- /dev/null +++ b/internal/editor/mark.go @@ -0,0 +1,43 @@ +package editor + +////////////////////// +// Marking Commands // +////////////////////// + +// +// Set Mark / Move to Mark +// + +// m : Mark current cursor position labelled by . +func (ed *Editor) MarkSet(letter rune) { + ed.EnsureCommand() + ed.Unimplemented("MarkSet") +} + +// ` : Move cursor to marked position labelled by . +func (ed *Editor) MarkMoveTo(letter rune) { + ed.EnsureCommand() + ed.Unimplemented("MarkMoveTo") +} + +// ' : Move cursor to marked line labelled by . +func (ed *Editor) MarkMoveToLine(letter rune) { + ed.EnsureCommand() + ed.Unimplemented("MarkMoveToLine") +} + +// +// Move by Context +// + +// “ : Move cursor to previous position in context. +func (ed *Editor) MarkBack() { + ed.EnsureCommand() + ed.Unimplemented("MarkBack") +} + +// ” : Move cursor to previous line in context. +func (ed *Editor) MarkBackToLine() { + ed.EnsureCommand() + ed.Unimplemented("MarkBackToLine") +} diff --git a/internal/editor/misc.go b/internal/editor/misc.go index 7d83892..10c2ea4 100644 --- a/internal/editor/misc.go +++ b/internal/editor/misc.go @@ -1,6 +1,35 @@ package editor +//////////////////////////// +// Miscellaneous Commands // +//////////////////////////// + +// Ctrl-g : Show info such as current cursor position. +func (ed *Editor) MiscShowInfo() { + ed.EnsureCommand() + ed.Unimplemented("MiscShowInfo") +} + +// . : Repeat last edit. +func (ed *Editor) MiscRepeat(n int) { + ed.EnsureCommand() + ed.Unimplemented("MiscRepeat") +} + +// u : Undo. +func (ed *Editor) MiscUndo(n int) { + ed.EnsureCommand() + ed.Unimplemented("MiscUndo") +} + +// U : Restore current line to previous state. +func (ed *Editor) MiscRestore() { + ed.EnsureCommand() + ed.Unimplemented("MiscRestore") +} + // ZZ : Save and quit. func (ed *Editor) MiscSaveAndQuit() { + ed.EnsureCommand() ed.quit = true } diff --git a/internal/editor/move.go b/internal/editor/move.go index 52c50f0..2bc9aca 100644 --- a/internal/editor/move.go +++ b/internal/editor/move.go @@ -1,64 +1,62 @@ package editor +///////////////////// +// Motion Commands // +///////////////////// + +// +// Move by Character / Move by Line +// + // h : Move cursor left by character. func (ed *Editor) MoveLeft(n int) { - if ed.mode != ModeCommand { - panic("invalid state") - } + ed.EnsureCommand() ed.col -= n ed.Confine() } // j : Move cursor down by line. func (ed *Editor) MoveDown(n int) { - if ed.mode != ModeCommand { - panic("invalid state") - } + ed.EnsureCommand() ed.row += n ed.Confine() } // k : Move cursor up by line. func (ed *Editor) MoveUp(n int) { - if ed.mode != ModeCommand { - panic("invalid state") - } + ed.EnsureCommand() ed.row -= n ed.Confine() } // l : Move cursor right by character. func (ed *Editor) MoveRight(n int) { - if ed.mode != ModeCommand { - panic("invalid state") - } + ed.EnsureCommand() ed.col += n ed.Confine() } +// +// Move in Line +// + // 0 : Move cursor to start of current line. func (ed *Editor) MoveToStart() { - if ed.mode != ModeCommand { - panic("invalid state") - } + ed.EnsureCommand() ed.col = 0 - // col is already confined + ed.Confine() // redundant } // $ : Move cursor to end of current line. func (ed *Editor) MoveToEnd() { - if ed.mode != ModeCommand { - panic("invalid state") - } + ed.EnsureCommand() ed.col = ed.RuneCount() - 1 ed.Confine() } // ^ : Move cursor to first non-blank character of current line. func (ed *Editor) MoveToNonBlank() { - if ed.mode != ModeCommand { - panic("invalid state") - } + ed.EnsureCommand() line := ed.CurrentLine() i := 0 for _, r := range line { @@ -74,9 +72,149 @@ func (ed *Editor) MoveToNonBlank() { // | : Move cursor to column of current line. // (Note: Proper vi's column number is visual-based, but levi' is rune-based.) func (ed *Editor) MoveToColumn(n int) { - if ed.mode != ModeCommand { - panic("invalid state") - } + ed.EnsureCommand() ed.col = n - 1 ed.Confine() } + +// +// Move by Word / Move by Loose Word +// + +// w : Move cursor forward by word. +func (ed *Editor) MoveByWord(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveByWord") +} + +// b : Move cursor backward by word. +func (ed *Editor) MoveBackwardByWord(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveBackwardByWord") +} + +// e : Move cursor to end of word. +func (ed *Editor) MoveToEndOfWord(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveToEndOfWord") +} + +// W : Move cursor forward by loose word. +func (ed *Editor) MoveByLooseWord(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveByLooseWord") +} + +// B : Move cursor backward by loose word. +func (ed *Editor) MoveBackwardByLooseWord(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveBackwardByLooseWord") +} + +// E : Move cursor to end of loose word. +func (ed *Editor) MoveToEndOfLooseWord(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveToEndOfLooseWord") +} + +// +// Move by Line +// + +// Enter, + : Move cursor to first non-blank character of next line. +func (ed *Editor) MoveToNonBlankOfNextLine(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveToNonBlankOfNextLine") +} + +// - : Move cursor to first non-blank character of previous line. +func (ed *Editor) MoveToNonBlankOfPrevLine(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveToNonBlankOfPrevLine") +} + +// G : Move cursor to last line. +func (ed *Editor) MoveToLastLine() { + ed.EnsureCommand() + ed.Unimplemented("MoveToLastLine") +} + +// G : Move cursor to line . +func (ed *Editor) MoveToLine(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveToLine") +} + +// +// Move by Block +// + +// ) : Move cursor forward by sentence. +func (ed *Editor) MoveBySentence(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveBySentence") +} + +// ( : Move cursor backward by sentence. +func (ed *Editor) MoveBackwardBySentence(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveBackwardBySentence") +} + +// } : Move cursor forward by paragraph. +func (ed *Editor) MoveByParagraph(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveByParagraph") +} + +// { : Move cursor backward by paragraph. +func (ed *Editor) MoveBackwardByParagraph(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveBackwardByParagraph") +} + +// ]] : Move cursor forward by section. +func (ed *Editor) MoveBySection(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveBySection") +} + +// [[ : Move cursor backward by section. +func (ed *Editor) MoveBackwardBySection(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveBackwardBySection") +} + +// +// Move in View +// + +// H : Move cursor to top of view. +func (ed *Editor) MoveToTopOfView() { + ed.EnsureCommand() + ed.Unimplemented("MoveToTopOfView") +} + +// M : Move cursor to middle of view. +func (ed *Editor) MoveToMiddleOfView() { + ed.EnsureCommand() + ed.Unimplemented("MoveToMiddleOfView") +} + +// L : Move cursor to bottom of view. +func (ed *Editor) MoveToBottomOfView() { + ed.EnsureCommand() + ed.Unimplemented("MoveToBottomOfView") +} + +// H : Move cursor below lines from top of view. +func (ed *Editor) MoveToBelowTopOfView(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveToBelowTopOfView") +} + +// L : Move cursor above lines from bottom of view. +func (ed *Editor) MoveToAboveBottomOfView(n int) { + ed.EnsureCommand() + ed.Unimplemented("MoveToAboveBottomOfView") +} diff --git a/internal/editor/op.go b/internal/editor/op.go index dafe5e4..e5e2746 100644 --- a/internal/editor/op.go +++ b/internal/editor/op.go @@ -1,10 +1,78 @@ package editor +/////////////////////////////////////////////// +// Operator Commands (Copy / Delte / Change) // +/////////////////////////////////////////////// + +// +// Copy (Yank) +// + +// yy, Y : Copy current line. +func (ed *Editor) OpCopyLine(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpCopyLine") +} + +// y : Copy region from current cursor to destination of motion . +func (ed *Editor) OpCopyRegion(start Loc, end Loc) { + ed.EnsureCommand() + ed.Unimplemented("OpCopyRegion") +} + +// y : Copy region from current cursor to destination of motion . +func (ed *Editor) OpCopyLineRegion(start int, end int) { + ed.EnsureCommand() + ed.Unimplemented("OpCopyLineRegion") +} + +// yw : Copy word. +func (ed *Editor) OpCopyWord(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpCopyWord") +} + +// y$ : Copy to end of current line. +func (ed *Editor) OpCopyToEnd(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpCopyToEnd") +} + +// "yy : Copy current line into register . +func (ed *Editor) OpCopyLineIntoReg(reg rune, n int) { + ed.EnsureCommand() + ed.Unimplemented("OpCopyLineIntoReg") +} + +// +// Paste (Put) +// + +// p : Paste after cursor. +func (ed *Editor) OpPaste(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpPaste") +} + +// P : Paste before cursor. +func (ed *Editor) OpPasteBefore(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpPasteBefore") +} + +// "p : Paste from register . +func (ed *Editor) OpPasteFromReg(reg rune, n int) { + ed.EnsureCommand() + ed.Unimplemented("OpPasteFromReg") +} + +// +// Delete +// + // x : Delete character under cursor. func (ed *Editor) OpDelete(n int) { - if ed.mode != ModeCommand { - panic("invalid state") - } + ed.EnsureCommand() if len(ed.CurrentLine()) < 1 { ed.Ring("nothing to delete, line is empty") return @@ -19,3 +87,84 @@ func (ed *Editor) OpDelete(n int) { } ed.Confine() } + +// X : Delete character before cursor. +func (ed *Editor) OpDeleteBefore(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpDeleteBefore") +} + +// dd : Delete current line. +func (ed *Editor) OpDeleteLine(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpDeleteLine") +} + +// d : Delete region from current cursor to destination of motion . +func (ed *Editor) OpDeleteRegion(start Loc, end Loc) { + ed.EnsureCommand() + ed.Unimplemented("OpDeleteRegion") +} + +// d : Delete region from current cursor to destination of motion . +func (ed *Editor) OpDeleteLineRegion(start int, end int) { + ed.EnsureCommand() + ed.Unimplemented("OpDeleteLineRegion") +} + +// dw : Delete word. +func (ed *Editor) OpDeleteWord(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpDeleteWord") +} + +// d$, D : Delete to end of current line. +func (ed *Editor) OpDeleteToEnd(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpDeleteToEnd") +} + +// +// Change / Substitute +// + +// cc : Change current line. +func (ed *Editor) OpChangeLine(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpChangeLine") +} + +// c : Change region from current cursor to destination of motion . +func (ed *Editor) OpChangeRegion(start Loc, end Loc) { + ed.EnsureCommand() + ed.Unimplemented("OpChangeRegion") +} + +// c : Change region from current cursor to destination of motion . +func (ed *Editor) OpChangeLineRegion(start int, end int) { + ed.EnsureCommand() + ed.Unimplemented("OpChangeLineRegion") +} + +// cw : Change word. +func (ed *Editor) OpChangeWord(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpChangeWord") +} + +// C : Change to end of current line. +func (ed *Editor) OpChangeToEnd(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpChangeToEnd") +} + +// s : Substitute one character under cursor. +func (ed *Editor) OpSubst(n int) { + ed.EnsureCommand() + ed.Unimplemented("OpSubst") +} + +// S : Substtute current line (equals cc). +func (ed *Editor) OpSubstLine(n int) { + ed.OpChangeLine(n) +} diff --git a/internal/editor/parser.go b/internal/editor/parser.go index 098f820..fd1d156 100644 --- a/internal/editor/parser.go +++ b/internal/editor/parser.go @@ -56,12 +56,10 @@ var letterMoveSet = map[rune]struct{}{ 'F': {}, 't': {}, 'T': {}, - ';': {}, - ',': {}, } -func (p *Parser) ParseMove(noNum bool, num int, op string, letter rune) (Cmd, bool) { - switch op { +func (p *Parser) ParseMove(noNum bool, num int, mv string, letter rune) (Cmd, bool) { + switch mv { case "h": return Cmd{ Kind: CmdMoveLeft, @@ -95,22 +93,280 @@ func (p *Parser) ParseMove(noNum bool, num int, op string, letter rune) (Cmd, bo Num: num, }, true - // TODO + case "w": + return Cmd{ + Kind: CmdMoveByWord, + Num: num, + }, true + case "b": + return Cmd{ + Kind: CmdMoveBackwardByWord, + Num: num, + }, true + case "e": + return Cmd{ + Kind: CmdMoveToEndOfWord, + Num: num, + }, true + case "W": + return Cmd{ + Kind: CmdMoveByLooseWord, + Num: num, + }, true + case "B": + return Cmd{ + Kind: CmdMoveBackwardByLooseWord, + Num: num, + }, true + case "E": + return Cmd{ + Kind: CmdMoveToEndOfLooseWord, + Num: num, + }, true + + case "\r", "+": + return Cmd{ + Kind: CmdMoveToNonBlankOfNextLine, + Num: num, + }, true + case "-": + return Cmd{ + Kind: CmdMoveToNonBlankOfPrevLine, + Num: num, + }, true + case "G": + if noNum { + return Cmd{Kind: CmdMoveToLastLine}, true + } else { + return Cmd{ + Kind: CmdMoveToLine, + Num: num, + }, true + } + + case ")": + return Cmd{ + Kind: CmdMoveBySentence, + Num: num, + }, true + case "(": + return Cmd{ + Kind: CmdMoveBackwardBySentence, + Num: num, + }, true + case "}": + return Cmd{ + Kind: CmdMoveByParagraph, + Num: num, + }, true + case "{": + return Cmd{ + Kind: CmdMoveBackwardByParagraph, + Num: num, + }, true + case "]]": + return Cmd{ + Kind: CmdMoveBySection, + Num: num, + }, true + case "[[": + return Cmd{ + Kind: CmdMoveBackwardBySection, + Num: num, + }, true + + case "H": + if noNum { + return Cmd{Kind: CmdMoveToTopOfView}, true + } else { + return Cmd{ + Kind: CmdMoveToBelowTopOfView, + Num: num, + }, true + } + case "M": + return Cmd{Kind: CmdMoveToMiddleOfView}, true + case "L": + if noNum { + return Cmd{Kind: CmdMoveToBottomOfView}, true + } else { + return Cmd{ + Kind: CmdMoveToAboveBottomOfView, + Num: num, + }, true + } + } + + return p.ParseFind(num, mv, letter) +} + +func (p *Parser) ParseLetter(num int, op string, letter rune) (Cmd, bool) { + if letter == 0 { + return Cmd{}, false + } + + switch op { + case "m": + return Cmd{ + Kind: CmdMarkSet, + Letter: letter, + }, true + case "`": + if letter == '`' { + return Cmd{Kind: CmdMarkBack}, true + } else { + return Cmd{ + Kind: CmdMarkMoveTo, + Letter: letter, + }, true + } + case "'": + if letter == '\'' { + return Cmd{Kind: CmdMarkBackToLine}, true + } else { + return Cmd{ + Kind: CmdMarkMoveToLine, + Letter: letter, + }, true + } + } + + switch op { + case "r": + return Cmd{ + Kind: CmdEditReplace, + Num: num, + Letter: letter, + }, true } return Cmd{}, false } -func (p *Parser) ParseLetter(num int, op string, letter rune) (Cmd, bool) { +func (p *Parser) ParseView(num int, op string) (Cmd, bool) { switch op { - case "m": - return Cmd{}, false // TODO - case "'": - return Cmd{}, false // TODO - case "`": - return Cmd{}, false // TODO - case "r": - return Cmd{}, false // TODO + case "\x06": // Ctrl-f + return Cmd{ + Kind: CmdViewDown, + Num: num, + }, true + case "\x02": // Ctrl-b + return Cmd{ + Kind: CmdViewUp, + Num: num, + }, true + case "\x04": // Ctrl-d + return Cmd{ + Kind: CmdViewDownHalf, + Num: num, + }, true + case "\x15": // Ctrl-u + return Cmd{ + Kind: CmdViewUpHalf, + Num: num, + }, true + case "\x19": // Ctrl-y + return Cmd{ + Kind: CmdViewDownLine, + Num: num, + }, true + case "\x05": // Ctrl-e + return Cmd{ + Kind: CmdViewUpLine, + Num: num, + }, true + + case "z\r": + return Cmd{Kind: CmdViewToTop}, true + case "z.": + return Cmd{Kind: CmdViewToMiddle}, true + case "z-": + return Cmd{Kind: CmdViewToBottom}, true + + case "\x0c": // Ctrl-l + return Cmd{Kind: CmdViewRedraw}, true + } + + return Cmd{}, false +} + +func (p *Parser) ParseSearch(op string, pat string) (Cmd, bool) { + switch op { + case "/": + if pat == "" { + return Cmd{Kind: CmdSearchRepeatForward}, true + } else { + return Cmd{ + Kind: CmdSearchForward, + Pat: pat, + }, true + } + case "?": + if pat == "" { + return Cmd{Kind: CmdSearchRepeatBackward}, true + } else { + return Cmd{ + Kind: CmdSearchBackward, + Pat: pat, + }, true + } + case "n": + return Cmd{Kind: CmdSearchNextMatch}, true + case "N": + return Cmd{Kind: CmdSearchPrevMatch}, true + } + + return Cmd{}, false +} + +func (p *Parser) ParseFind(num int, op string, letter rune) (Cmd, bool) { + switch op { + case "f": + if letter == 0 { + return Cmd{}, false + } + return Cmd{ + Kind: CmdFindForward, + Num: num, + Letter: letter, + }, true + case "F": + if letter == 0 { + return Cmd{}, false + } + return Cmd{ + Kind: CmdFindBackward, + Num: num, + Letter: letter, + }, true + case "t": + if letter == 0 { + return Cmd{}, false + } + return Cmd{ + Kind: CmdFindBeforeForward, + Num: num, + Letter: letter, + }, true + case "T": + if letter == 0 { + return Cmd{}, false + } + return Cmd{ + Kind: CmdFindBeforeBackward, + Num: num, + Letter: letter, + }, true + case ";": + return Cmd{ + Kind: CmdFindNextMatch, + Num: num, + }, true + case ",": + return Cmd{ + Kind: CmdFindPrevMatch, + Num: num, + }, true } return Cmd{}, false @@ -128,19 +384,55 @@ func (p *Parser) ParseInsert(num int, op string) (Cmd, bool) { Kind: CmdInsertAfter, Num: num, }, true + case "I": + return Cmd{ + Kind: CmdInsertBeforeNonBlank, + Num: num, + }, true + case "A": + return Cmd{ + Kind: CmdInsertAfterEnd, + Num: num, + }, true + case "R": + return Cmd{ + Kind: CmdInsertOverwrite, + Num: num, + }, true - // TODO + case "o": + return Cmd{ + Kind: CmdInsertOpenBelow, + Num: num, + }, true + case "O": + return Cmd{ + Kind: CmdInsertOpenAbove, + Num: num, + }, true } return Cmd{}, false } -func (p *Parser) ParseMisc(op string) (Cmd, bool) { +func (p *Parser) ParseMisc(num int, op string) (Cmd, bool) { switch op { + case "\x07": // Ctrl-g + return Cmd{Kind: CmdMiscShowInfo}, true + case ".": + return Cmd{ + Kind: CmdMiscRepeat, + Num: num, + }, true + case "u": + return Cmd{ + Kind: CmdMiscUndo, + Num: num, + }, true + case "U": + return Cmd{Kind: CmdMiscRestore}, true case "ZZ": return Cmd{Kind: CmdMiscSaveAndQuit}, true - - // TODO } return Cmd{}, false @@ -148,19 +440,191 @@ func (p *Parser) ParseMisc(op string) (Cmd, bool) { func (p *Parser) ParseOp(reg rune, num int, op string, noSubnum bool, subnum int, mv string) (Cmd, bool) { switch op { + case "yy", "Y": + if reg == 0 { + return Cmd{ + Kind: CmdOpCopyLine, + Num: num, + }, true + } else { + return Cmd{ + Kind: CmdOpCopyLineIntoReg, + Num: num, + Reg: reg, + }, true + } + case "y": + /* + return Cmd{ + Kind: CmdOpCopyRegion, + Start: Loc{}, // TODO + End: Loc{}, // TODO + }, true + return Cmd{ + Kind: CmdOpCopyLineRegion, + StartRow: 0, // TODO + EndRow: 0, // TODO + }, true + */ + case "yw": + return Cmd{ + Kind: CmdOpCopyWord, + Num: num, + }, true + case "y$": + return Cmd{ + Kind: CmdOpCopyToEnd, + Num: num, + }, true + + case "p": + if reg == 0 { + return Cmd{ + Kind: CmdOpPaste, + Num: num, + }, true + } else { + return Cmd{ + Kind: CmdOpPasteFromReg, + Num: num, + Reg: reg, + }, true + } + case "P": + return Cmd{ + Kind: CmdOpPasteBefore, + Num: num, + }, true + case "x": return Cmd{ Kind: CmdOpDelete, Num: num, }, true + case "X": + return Cmd{ + Kind: CmdOpDeleteBefore, + Num: num, + }, true + case "dd": + return Cmd{ + Kind: CmdOpDeleteLine, + Num: num, + }, true + case "d": + /* + return Cmd{ + Kind: CmdOpDeleteRegion, + Start: Loc{}, // TODO + End: Loc{}, // TODO + }, true + return Cmd{ + Kind: CmdOpDeleteLineRegion, + StartRow: 0, // TODO + EndRow: 0, // TODO + }, true + */ + case "dw": + return Cmd{ + Kind: CmdOpDeleteWord, + Num: num, + }, true + case "d$", "D": + return Cmd{ + Kind: CmdOpDeleteToEnd, + Num: num, + }, true - // TODO + case "cc": + return Cmd{ + Kind: CmdOpChangeLine, + Num: num, + }, true + case "c": + /* + return Cmd{ + Kind: CmdOpChangeRegion, + Start: Loc{}, // TODO + End: Loc{}, // TODO + }, true + return Cmd{ + Kind: CmdOpChangeLineRegion, + StartRow: 0, // TODO + EndRow: 0, // TODO + }, true + */ + case "cw": + return Cmd{ + Kind: CmdOpChangeWord, + Num: num, + }, true + case "C": + return Cmd{ + Kind: CmdOpChangeToEnd, + Num: num, + }, true + case "s": + return Cmd{ + Kind: CmdOpSubst, + Num: num, + }, true + case "S": + return Cmd{ + Kind: CmdOpSubstLine, + Num: num, + }, true + } + + return Cmd{}, false +} + +func (p *Parser) ParseEdit(num int, op string, noSubnum bool, subnum int, mv string) (Cmd, bool) { + switch op { + case "J": + return Cmd{ + Kind: CmdEditJoin, + Num: num, + }, true + case ">>": + return Cmd{ + Kind: CmdEditIndent, + Num: num, + }, true + case "<<": + return Cmd{ + Kind: CmdEditOutdent, + Num: num, + }, true + case ">": + /* + return Cmd{ + Kind: CmdEditIndentRegion, + Start: Loc{}, // TODO + End: Loc{}, // TODO + }, true + */ + case "<": + /* + return Cmd{ + Kind: CmdEditOutdentRegion, + Start: Loc{}, // TODO + End: Loc{}, // TODO + }, true + */ } return Cmd{}, false } var compoundSet = map[rune]struct{}{ + ']': {}, + '[': {}, + + '`': {}, + '\'': {}, + + 'z': {}, + 'y': {}, 'd': {}, 'c': {}, @@ -174,17 +638,15 @@ func (p *Parser) Parse() (Cmd, bool) { return Cmd{}, false } - if len(p.buf) == 1 { - if p.buf[0] == '0' { // special - return Cmd{Kind: CmdMoveToStart}, true - } + if p.buf[0] == '0' { // special + return Cmd{Kind: CmdMoveToStart}, true } i := 0 var reg rune = 0 - if p.buf[i] == '"' { - if len(p.buf) > i+1 { - reg = p.buf[i+1] + if p.buf[0] == '"' { + if len(p.buf) > 1 { + reg = p.buf[1] i += 2 } } @@ -196,9 +658,9 @@ func (p *Parser) Parse() (Cmd, bool) { } i++ } - noNum := i == iPrev + noNum := i <= iPrev num := 1 - if i > 0 { + if i > iPrev { s := string(p.buf[iPrev:i]) n, err := strconv.Atoi(s) if err != nil { @@ -207,6 +669,37 @@ func (p *Parser) Parse() (Cmd, bool) { num = n } + if i < len(p.buf) { + var letter rune = 0 + _, ok := letterOpSet[p.buf[i]] + if ok { + if i+1 >= len(p.buf) { + return Cmd{}, false + } + op := string(p.buf[i : i+1]) + letter = p.buf[i+1] + cmd, ok := p.ParseLetter(num, op, letter) + if ok { + return cmd, true + } + } + _, ok = letterMoveSet[p.buf[i]] + if ok { + if i+1 >= len(p.buf) { + return Cmd{}, false + } + mv := string(p.buf[i : i+1]) + letter = p.buf[i+1] + cmd, ok := p.ParseFind(num, mv, letter) + if ok { + return cmd, true + } + } + if letter != 0 { + return Cmd{Kind: CmdInvalid}, true + } + } + iPrev = i for i < len(p.buf) { if p.buf[i] >= '0' && p.buf[i] <= '9' { @@ -218,31 +711,6 @@ func (p *Parser) Parse() (Cmd, bool) { return Cmd{}, false } - if i+1 < len(p.buf) { - var letter rune = 0 - _, ok := letterOpSet[p.buf[i]] - if ok { - op := string(p.buf[i : i+1]) - letter = p.buf[i+1] - cmd, ok := p.ParseLetter(num, op, letter) - if ok { - return cmd, true - } - } - _, ok = letterMoveSet[p.buf[i]] - if ok { - mv := string(p.buf[i : i+1]) - letter = p.buf[i+1] - cmd, ok := p.ParseMove(noNum, num, mv, letter) - if ok { - return cmd, true - } - } - if letter != 0 { - return Cmd{Kind: CmdInvalid}, true - } - } - mv := string(p.buf[iPrev:i]) cmd, ok := p.ParseMove(noNum, num, mv, 0) @@ -252,11 +720,23 @@ func (p *Parser) Parse() (Cmd, bool) { op := mv opFirst := p.buf[iPrev] + cmd, ok = p.ParseView(num, op) + if ok { + return cmd, true + } + pat := "" + if op == "/" || op == "?" { + // TODO input pat + } + cmd, ok = p.ParseSearch(op, pat) + if ok { + return cmd, true + } cmd, ok = p.ParseInsert(num, op) if ok { return cmd, true } - cmd, ok = p.ParseMisc(op) + cmd, ok = p.ParseMisc(num, op) if ok { return cmd, true } @@ -268,7 +748,7 @@ func (p *Parser) Parse() (Cmd, bool) { } i++ } - noSubnum := i == iPrev + noSubnum := i <= iPrev subnum := 1 if i > iPrev { s := string(p.buf[iPrev:i]) @@ -279,20 +759,14 @@ func (p *Parser) Parse() (Cmd, bool) { subnum = n } - iPrev = i - for i < len(p.buf) { - if p.buf[i] >= '0' && p.buf[i] <= '9' { - break - } - i++ - } - var letter rune = 0 - if i+1 < len(p.buf) { + if i < len(p.buf) { _, ok := letterMoveSet[p.buf[i]] if ok { - mv = string(p.buf[i : i+1]) - letter = p.buf[i+1] + if i+1 < len(p.buf) { + mv = string(p.buf[i : i+1]) + letter = p.buf[i+1] + } } } @@ -307,6 +781,10 @@ func (p *Parser) Parse() (Cmd, bool) { if ok { return cmd, true } + cmd, ok = p.ParseEdit(num, op, noSubnum, subnum, mv) + if ok { + return cmd, true + } if len(op) < 2 { _, ok := compoundSet[opFirst] diff --git a/internal/editor/run.go b/internal/editor/run.go index a0e9c24..506d371 100644 --- a/internal/editor/run.go +++ b/internal/editor/run.go @@ -6,7 +6,9 @@ func (ed *Editor) Run(c Cmd) bool { ed.Ring("not (yet) a vi command [" + ed.parser.String() + "]") ed.parser.Clear() return true + } + switch c.Kind { case CmdMoveLeft: ed.MoveLeft(c.Num) return true @@ -33,28 +35,305 @@ func (ed *Editor) Run(c Cmd) bool { ed.MoveToColumn(c.Num) return true - // TODO + case CmdMoveByWord: + ed.MoveByWord(c.Num) + return true + case CmdMoveBackwardByWord: + ed.MoveBackwardByWord(c.Num) + return true + case CmdMoveToEndOfWord: + ed.MoveToEndOfWord(c.Num) + return true + case CmdMoveByLooseWord: + ed.MoveByLooseWord(c.Num) + return true + case CmdMoveBackwardByLooseWord: + ed.MoveBackwardByLooseWord(c.Num) + return true + case CmdMoveToEndOfLooseWord: + ed.MoveToEndOfLooseWord(c.Num) + return true + case CmdMoveToNonBlankOfNextLine: + ed.MoveToNonBlankOfNextLine(c.Num) + return true + case CmdMoveToNonBlankOfPrevLine: + ed.MoveToNonBlankOfPrevLine(c.Num) + return true + case CmdMoveToLastLine: + ed.MoveToLastLine() + return true + case CmdMoveToLine: + ed.MoveToLine(c.Num) + return true + + case CmdMoveBySentence: + ed.MoveBySentence(c.Num) + return true + case CmdMoveBackwardBySentence: + ed.MoveBackwardBySentence(c.Num) + return true + case CmdMoveByParagraph: + ed.MoveByParagraph(c.Num) + return true + case CmdMoveBackwardByParagraph: + ed.MoveBackwardByParagraph(c.Num) + return true + case CmdMoveBySection: + ed.MoveBySection(c.Num) + return true + case CmdMoveBackwardBySection: + ed.MoveBackwardBySection(c.Num) + return true + + case CmdMoveToTopOfView: + ed.MoveToTopOfView() + return true + case CmdMoveToMiddleOfView: + ed.MoveToMiddleOfView() + return true + case CmdMoveToBottomOfView: + ed.MoveToBottomOfView() + return true + case CmdMoveToBelowTopOfView: + ed.MoveToBelowTopOfView(c.Num) + return true + case CmdMoveToAboveBottomOfView: + ed.MoveToAboveBottomOfView(c.Num) + return true + } + + switch c.Kind { + case CmdMarkSet: + ed.MarkSet(c.Letter) + return true + case CmdMarkMoveTo: + ed.MarkMoveTo(c.Letter) + return true + case CmdMarkMoveToLine: + ed.MarkMoveToLine(c.Letter) + return true + + case CmdMarkBack: + ed.MarkBack() + return true + case CmdMarkBackToLine: + ed.MarkBackToLine() + return true + } + + switch c.Kind { + case CmdViewDown: + ed.ViewDown(c.Num) + return true + case CmdViewUp: + ed.ViewUp(c.Num) + return true + case CmdViewDownHalf: + ed.ViewDownHalf(c.Num) + return true + case CmdViewUpHalf: + ed.ViewUpHalf(c.Num) + return true + case CmdViewDownLine: + ed.ViewDownLine(c.Num) + return true + case CmdViewUpLine: + ed.ViewUpLine(c.Num) + return true + + case CmdViewToTop: + ed.ViewToTop() + return true + case CmdViewToMiddle: + ed.ViewToMiddle() + return true + case CmdViewToBottom: + ed.ViewToBottom() + return true + + case CmdViewRedraw: + ed.ViewRedraw() + return true + } + + switch c.Kind { + case CmdSearchForward: + ed.SearchForward(c.Pat) + return true + case CmdSearchBackward: + ed.SearchBackward(c.Pat) + return true + case CmdSearchNextMatch: + ed.SearchNextMatch() + return true + case CmdSearchPrevMatch: + ed.SearchPrevMatch() + return true + case CmdSearchRepeatForward: + ed.SearchRepeatForward() + return true + case CmdSearchRepeatBackward: + ed.SearchRepeatBackward() + return true + } + + switch c.Kind { + case CmdFindForward: + ed.FindForward(c.Letter, c.Num) + return true + case CmdFindBackward: + ed.FindBackward(c.Letter, c.Num) + return true + case CmdFindBeforeForward: + ed.FindBeforeForward(c.Letter, c.Num) + return true + case CmdFindBeforeBackward: + ed.FindBeforeBackward(c.Letter, c.Num) + return true + case CmdFindNextMatch: + ed.FindNextMatch(c.Num) + return true + case CmdFindPrevMatch: + ed.FindPrevMatch(c.Num) + return true + } + + switch c.Kind { case CmdInsertBefore: ed.InsertBefore(c.Num) return true case CmdInsertAfter: ed.InsertAfter(c.Num) return true + case CmdInsertBeforeNonBlank: + ed.InsertBeforeNonBlank(c.Num) + return true + case CmdInsertAfterEnd: + ed.InsertAfterEnd(c.Num) + return true + case CmdInsertOverwrite: + ed.InsertOverwrite(c.Num) + return true - // TODO + case CmdInsertOpenBelow: + ed.InsertOpenBelow(c.Num) + return true + case CmdInsertOpenAbove: + ed.InsertOpenAbove(c.Num) + return true + + case CmdOpCopyLine: + ed.OpCopyLine(c.Num) + return true + case CmdOpCopyRegion: + ed.OpCopyRegion(c.Start, c.End) + return true + case CmdOpCopyLineRegion: + ed.OpCopyLineRegion(c.StartRow, c.EndRow) + return true + case CmdOpCopyWord: + ed.OpCopyWord(c.Num) + return true + case CmdOpCopyToEnd: + ed.OpCopyToEnd(c.Num) + return true + case CmdOpCopyLineIntoReg: + ed.OpCopyLineIntoReg(c.Reg, c.Num) + return true + + case CmdOpPaste: + ed.OpPaste(c.Num) + return true + case CmdOpPasteBefore: + ed.OpPasteBefore(c.Num) + return true + case CmdOpPasteFromReg: + ed.OpPasteFromReg(c.Reg, c.Num) + return true case CmdOpDelete: ed.OpDelete(c.Num) return true + case CmdOpDeleteBefore: + ed.OpDeleteBefore(c.Num) + return true + case CmdOpDeleteLine: + ed.OpDeleteLine(c.Num) + return true + case CmdOpDeleteRegion: + ed.OpDeleteRegion(c.Start, c.End) + return true + case CmdOpDeleteLineRegion: + ed.OpDeleteLineRegion(c.StartRow, c.EndRow) + return true + case CmdOpDeleteWord: + ed.OpDeleteWord(c.Num) + return true + case CmdOpDeleteToEnd: + ed.OpDeleteToEnd(c.Num) + return true - // TODO + case CmdOpChangeLine: + ed.OpChangeLine(c.Num) + return true + case CmdOpChangeRegion: + ed.OpChangeRegion(c.Start, c.End) + return true + case CmdOpChangeLineRegion: + ed.OpChangeLineRegion(c.StartRow, c.EndRow) + return true + case CmdOpChangeWord: + ed.OpChangeWord(c.Num) + return true + case CmdOpChangeToEnd: + ed.OpChangeToEnd(c.Num) + return true + case CmdOpSubst: + ed.OpSubst(c.Num) + return true + case CmdOpSubstLine: + ed.OpSubstLine(c.Num) + return true + } + switch c.Kind { + case CmdEditReplace: + ed.EditReplace(c.Letter, c.Num) + return true + case CmdEditJoin: + ed.EditJoin(c.Num) + return true + case CmdEditIndent: + ed.EditIndent(c.Num) + return true + case CmdEditOutdent: + ed.EditOutdent(c.Num) + return true + case CmdEditIndentRegion: + ed.EditIndentRegion(c.Start, c.End) + return true + case CmdEditOutdentRegion: + ed.EditOutdentRegion(c.Start, c.End) + return true + } + + switch c.Kind { + case CmdMiscShowInfo: + ed.MiscShowInfo() + return true + case CmdMiscRepeat: + ed.MiscRepeat(c.Num) + return true + case CmdMiscUndo: + ed.MiscUndo(c.Num) + return true + case CmdMiscRestore: + ed.MiscRestore() + return true case CmdMiscSaveAndQuit: ed.MiscSaveAndQuit() return true - - // TODO } return false diff --git a/internal/editor/search.go b/internal/editor/search.go new file mode 100644 index 0000000..8009e32 --- /dev/null +++ b/internal/editor/search.go @@ -0,0 +1,41 @@ +package editor + +///////////////////// +// Search Commands // +///////////////////// + +// / Enter : Search forward. +func (ed *Editor) SearchForward(s string) { + ed.EnsureCommand() + ed.Unimplemented("SearchForward") +} + +// ? Enter : Search backward. +func (ed *Editor) SearchBackward(s string) { + ed.EnsureCommand() + ed.Unimplemented("SearchBackward") +} + +// n : Search next match. +func (ed *Editor) SearchNextMatch() { + ed.EnsureCommand() + ed.Unimplemented("SearchNextMatch") +} + +// N : Search previous match. +func (ed *Editor) SearchPrevMatch() { + ed.EnsureCommand() + ed.Unimplemented("SearchPrevMatch") +} + +// / Enter : Repeat last search forward. +func (ed *Editor) SearchRepeatForward() { + ed.EnsureCommand() + ed.Unimplemented("SearchRepeatForward") +} + +// ? Enter : Repeat last search backward. +func (ed *Editor) SearchRepeatBackward() { + ed.EnsureCommand() + ed.Unimplemented("SearchRepeatBackward") +} diff --git a/internal/editor/view.go b/internal/editor/view.go index 1b2b288..3e39932 100644 --- a/internal/editor/view.go +++ b/internal/editor/view.go @@ -1,110 +1,77 @@ package editor -import ( - "unicode/utf8" +/////////////////// +// View Commands // +/////////////////// - "tea.kareha.org/lab/termi" -) +// +// Scroll by View Height / Scroll by Line +// -func (ed *Editor) LineHeight(line string) int { - rc := utf8.RuneCountInString(line) - width := termi.StringWidth(line, rc) - return 1 + max(width-1, 0)/ed.w +// Ctrl-f : Scroll down by view height. +func (ed *Editor) ViewDown(n int) { + ed.EnsureCommand() + ed.Unimplemented("ViewDown") } -func (ed *Editor) DrawBuffer() { - y := 0 - for i := ed.vrow; i < len(ed.lines); i++ { - line := ed.Line(i) - - termi.MoveCursor(0, y) - termi.Draw(line) - - y += ed.LineHeight(line) - if y >= ed.h-1 { - break - } - } - - for ; y < ed.h-1; y++ { - termi.MoveCursor(0, y) - termi.Draw("~") - } +// Ctrl-b : Scroll up by view height. +func (ed *Editor) ViewUp(n int) { + ed.EnsureCommand() + ed.Unimplemented("ViewUp") } -func (ed *Editor) DrawStatus() { - var m string - switch ed.mode { - case ModeCommand: - m = "vi command" - case ModeInsert: - m = "vi insert" - case ModeSearch: - m = "vi search" - case ModePrompt: - m = "vi prompt" - default: - panic("invalid mode") - } - - termi.MoveCursor(0, ed.h-1) - if ed.message != "" { - termi.EnableInvert() - termi.Print(ed.message) - termi.DisableInvert() - ed.message = "" - } else { - termi.Printf("[%s] %s %d,%d %s", ed.parser.Cache(), m, ed.row, ed.col, ed.path) - } +// Ctrl-d : Scroll down by half view height. +func (ed *Editor) ViewDownHalf(n int) { + ed.EnsureCommand() + ed.Unimplemented("ViewDownHalf") } -func (ed *Editor) UpdateCursor() { - // XXX approximation - width := termi.StringWidth(ed.CurrentLine(), ed.col) - ed.x = width % ed.w - dy := width / ed.w - - if ed.row < ed.vrow { - ed.vrow = ed.row - } - - y := 0 - for i := ed.vrow; i < ed.row; i++ { - y += ed.LineHeight(ed.lines[i]) - } - ed.y = y + dy - - for ed.y >= ed.h-1 { - ed.vrow++ - - y := 0 - for i := ed.vrow; i < ed.row; i++ { - y += ed.LineHeight(ed.lines[i]) - } - ed.y = y + dy - } +// Ctrl-u : Scroll up by half view height. +func (ed *Editor) ViewUpHalf(n int) { + ed.EnsureCommand() + ed.Unimplemented("ViewUpHalf") } -func (ed *Editor) Repaint() { - w, h := termi.Size() - ed.w = w - ed.h = h - - termi.HideCursor() - - termi.Clear() - termi.HomeCursor() - - ed.UpdateCursor() - - ed.DrawBuffer() - ed.DrawStatus() - - termi.MoveCursor(ed.x, ed.y) - - termi.ShowCursor() +// Ctrl-y : Scroll down by line. +func (ed *Editor) ViewDownLine(n int) { + ed.EnsureCommand() + ed.Unimplemented("ViewDownLine") } -func (ed *Editor) Draw() { - ed.Repaint() +// Ctrl-e : Scroll up by line. +func (ed *Editor) ViewUpLine(n int) { + ed.EnsureCommand() + ed.Unimplemented("ViewUpLine") +} + +// +// Reposition +// + +// z Enter : Reposition cursor line to top of view. +func (ed *Editor) ViewToTop() { + ed.EnsureCommand() + ed.Unimplemented("ViewToTop") +} + +// z. : Reposition cursor line middle of view. +func (ed *Editor) ViewToMiddle() { + ed.EnsureCommand() + ed.Unimplemented("ViewToMiddle") +} + +// z- : Reposition cursor line bottom of view. +func (ed *Editor) ViewToBottom() { + ed.EnsureCommand() + ed.Unimplemented("ViewToBottom") +} + +// +// Redraw +// + +// Ctrl-l : Redraw view. +func (ed *Editor) ViewRedraw() { + ed.EnsureCommand() + ed.Unimplemented("ViewRedraw") }