From 310afed6846021f9b46ee783e7f2a25d41b630a4 Mon Sep 17 00:00:00 2001 From: Kyren223 Date: Thu, 12 Dec 2024 19:38:13 +0200 Subject: Implemented f/t/F/T motions in viminput --- internal/client/ui/viminput/helpers.go | 13 +++++ internal/client/ui/viminput/viminput.go | 98 ++++++++++++++++++++++++++++++--- 2 files changed, 104 insertions(+), 7 deletions(-) (limited to 'internal/client') diff --git a/internal/client/ui/viminput/helpers.go b/internal/client/ui/viminput/helpers.go index c3dcdb1..bd12957 100644 --- a/internal/client/ui/viminput/helpers.go +++ b/internal/client/ui/viminput/helpers.go @@ -18,3 +18,16 @@ func IsGrouped(r1, r2 rune) bool { } return !IsKeyword(r2) && !unicode.IsSpace(r2) } + +func SearchChar(line []rune, i, dir int, c rune) (index int, ok bool) { + found := false + for i > 0 && i < len(line) { + if line[i] == c { + found = true + break + } + i += dir + } + + return i, found +} diff --git a/internal/client/ui/viminput/viminput.go b/internal/client/ui/viminput/viminput.go index 7e47e7c..10d6d53 100644 --- a/internal/client/ui/viminput/viminput.go +++ b/internal/client/ui/viminput/viminput.go @@ -17,7 +17,11 @@ type ( LineDecoration = func(lnum int, m Model) string ) -const Unchanged = -1 +const ( + Unchanged = -1 + InvalidGoal = -1 + NullChar = 0 +) const ( NormalMode = iota @@ -42,6 +46,9 @@ type Model struct { mode int pending byte gmod bool + fchar byte + fmod byte + tlast bool focus bool width int @@ -58,10 +65,13 @@ func New(width, height int) Model { lines: [][]rune{[]rune("")}, cursorLine: 0, cursorColumn: 0, - goalColumn: -1, + goalColumn: InvalidGoal, mode: NormalMode, - pending: 0, + pending: NullChar, gmod: false, + fchar: NullChar, + fmod: NullChar, + tlast: false, focus: false, width: width, height: height, @@ -158,7 +168,7 @@ func (m *Model) SetCursorColumn(col int) { } else { m.cursorColumn = max(col, 0) } - m.goalColumn = -1 + m.goalColumn = InvalidGoal } func (m *Model) SetCursorLine(line int) { @@ -169,10 +179,10 @@ func (m *Model) SetCursorLine(line int) { if m.mode == NormalMode { toLength-- // In normal it's not (unless length == 0) } - if fromLength > toLength && m.goalColumn == -1 { + if fromLength > toLength && m.goalColumn == InvalidGoal { m.goalColumn = fromLength } - if m.goalColumn != -1 { + if m.goalColumn != InvalidGoal { m.cursorColumn = max(min(toLength, m.goalColumn), 0) } } @@ -396,6 +406,40 @@ func (m *Model) Motion(motion string) (line, col int) { m.gmod = false } + isF := motion == "f" || motion == "t" || motion == "F" || motion == "T" + if isF && m.fmod == NullChar { + m.fmod = motion[0] + return Unchanged, Unchanged + } + if m.fmod != NullChar { + defer func(m *Model) { m.fmod = NullChar }(m) + + if len(motion) != 1 { + return Unchanged, Unchanged + } + m.fchar = motion[0] + + dir := 1 + if m.fmod == 'F' || m.fmod == 'T' { + dir = -1 + } + line := m.lines[m.cursorLine] + i := m.cursorColumn + dir + + index, ok := SearchChar(line, i, dir, rune(m.fchar)) + if !ok { + return Unchanged, Unchanged + } + + if m.fmod == 't' || m.fmod == 'T' { + index -= dir + m.tlast = true + } else { + m.tlast = false + } + return Unchanged, index + } + switch motion { case "h": return Unchanged, m.cursorColumn - 1 @@ -445,6 +489,37 @@ func (m *Model) Motion(motion string) (line, col int) { case "G": return len(m.lines) - 1, Unchanged + case ",": + dir := -1 + line := m.lines[m.cursorLine] + i := m.cursorColumn + dir + if m.tlast { + i += dir + } + index, ok := SearchChar(line, i, dir, rune(m.fchar)) + if !ok { + return Unchanged, Unchanged + } + if m.tlast { + index -= dir + } + return Unchanged, index + case ";": + dir := 1 + line := m.lines[m.cursorLine] + i := m.cursorColumn + dir + if m.tlast { + i += dir + } + index, ok := SearchChar(line, i, dir, rune(m.fchar)) + if !ok { + return Unchanged, Unchanged + } + if m.tlast { + index -= dir + } + return Unchanged, index + // case "E": // for { // line := m.lines[m.cursorLine] @@ -480,8 +555,9 @@ func (m *Model) handleOpendingModeKeys(key tea.KeyMsg) { return } + ftmod := m.fmod == 'f' || m.fmod == 't' lnum, col := m.Motion(key.String()) - if m.gmod { + if m.gmod || m.fmod != NullChar { return } if lnum != Unchanged || col != Unchanged { @@ -491,6 +567,10 @@ func (m *Model) handleOpendingModeKeys(key tea.KeyMsg) { if col == Unchanged { col = m.cursorColumn } + if ftmod { + col++ // Adjust due to upper bound being exclusive + // For F/T (backwards), being exclusive is the correct + } if lnum == m.cursorLine { line := m.lines[m.cursorLine] @@ -507,6 +587,10 @@ func (m *Model) handleOpendingModeKeys(key tea.KeyMsg) { } m.SetCursorColumn(min(lower, len(line)-1)) + if ftmod && m.pending == 'c' { + m.SetCursorColumn(lower) // Ignore bound + // Only ok because of insert mode (same as NVIM) + } } else { lower := min(lnum, m.cursorLine) -- cgit v1.3.1