Skip to content

Commit e1fd682

Browse files
feat(jump)!: make dot-repeat jumping less intefere with regular jumping
Resolve #2054 Co-authored-by: Evgeni Chasnovski <evgeni.chasnovski@gmail.com>
1 parent f73140c commit e1fd682

5 files changed

Lines changed: 104 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ There are following change types:
3434
- Add new plugin integrations:
3535
- 'folke/snacks.nvim'
3636

37+
## mini.jump
38+
39+
### Evolve
40+
41+
- Make dot-repeat behave exactly as in clean Neovim, i.e. make it less interfere with regular jumping. This allows doing something like `dte` -> `fx` -> `.` to perform `dte` again and not `dfx` (which is the latest regular jumping state). By @abeldekat, PR #2284.
42+
3743
## mini.misc
3844

3945
### Evolve

doc/mini-jump.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Features:
1414
- Highlight (after customizable delay) all possible target characters and
1515
stop it after some (customizable) idle time.
1616

17-
- Normal, Visual, and Operator-pending (with full dot-repeat) modes are
18-
supported.
17+
- Normal, Visual, and Operator-pending (with dot-repeat as in clean Neovim)
18+
modes are supported.
1919

2020
This module follows vim's 'ignorecase' and 'smartcase' options. When
2121
'ignorecase' is set, f, F, t, T will match case-insensitively. When

lua/mini/jump.lua

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
--- - Highlight (after customizable delay) all possible target characters and
1313
--- stop it after some (customizable) idle time.
1414
---
15-
--- - Normal, Visual, and Operator-pending (with full dot-repeat) modes are
16-
--- supported.
15+
--- - Normal, Visual, and Operator-pending (with dot-repeat as in clean Neovim)
16+
--- modes are supported.
1717
---
1818
--- This module follows vim's 'ignorecase' and 'smartcase' options. When
1919
--- 'ignorecase' is set, f, F, t, T will match case-insensitively. When
@@ -181,6 +181,11 @@ MiniJump.state = {
181181
MiniJump.jump = function(target, backward, till, n_times)
182182
if H.is_disabled() then return end
183183

184+
-- Dot-repeat should not change the state, so save it to later restore
185+
local is_dot_repeat = MiniJump._is_expr and not MiniJump._is_expr_init
186+
MiniJump._is_expr, MiniJump._is_expr_init = nil, nil
187+
local state_snapshot = is_dot_repeat and vim.deepcopy(MiniJump.state) or nil
188+
184189
-- Cache inputs for future use
185190
H.update_state(target, backward, till, n_times)
186191

@@ -230,6 +235,12 @@ MiniJump.jump = function(target, backward, till, n_times)
230235
-- Track cursor position to account for movement not caught by `CursorMoved`
231236
H.cache.latest_cursor = H.get_cursor_data()
232237
H.cache.has_changed_cursor = not vim.deep_equal(H.cache.latest_cursor, init_cursor_data)
238+
239+
-- Restore the state if needed
240+
if is_dot_repeat then
241+
state_snapshot.jumping = true
242+
MiniJump.state = state_snapshot
243+
end
233244
end
234245

235246
--- Make smart jump
@@ -383,22 +394,29 @@ H.make_expr_jump = function(backward, till)
383394
return function()
384395
if H.is_disabled() then return '' end
385396

386-
H.update_state(nil, backward, till, vim.v.count1)
397+
local count = vim.v.count1
398+
H.update_state(nil, backward, till, count)
387399

388400
-- Ask for `target` for non-repeating jump as this will be used only in
389401
-- operator-pending mode. Dot-repeat is supported via expression-mapping.
390-
local is_repeat_jump = backward == nil or till == nil
391-
local target = is_repeat_jump and MiniJump.state.target or H.get_target()
392-
393-
-- Stop if user supplied invalid target
394-
if target == nil then return '<Esc>' end
402+
local isnt_repeat_jump = backward ~= nil and till ~= nil
403+
local target = isnt_repeat_jump and H.get_target() or nil
404+
if isnt_repeat_jump and target == nil then return '<Esc>' end
395405
H.update_state(target)
396406

397407
vim.schedule(function()
398408
if H.cache.has_changed_cursor then return end
399409
vim.cmd('undo!')
400410
end)
401-
return 'v<Cmd>lua MiniJump.jump()<CR>'
411+
412+
-- Set a flag to distinguish first call from dot-repeat
413+
MiniJump._is_expr_init = true
414+
415+
-- Encode state in expression for dot-repeat. Important to use `target=nil`
416+
-- for `repeat_jump` case to have it using latest jumping state during
417+
-- dot-repeat also (as does `nvim --clean`).
418+
local args = string.format('%s,%s,%s,%s', vim.inspect(target), backward, till, count)
419+
return 'v<Cmd>lua MiniJump._is_expr=true; MiniJump.jump(' .. args .. ')<CR>'
402420
end
403421
end
404422

readmes/mini-jump.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ https://user-images.githubusercontent.com/24854248/173044762-f0f50a73-02df-4432-
3333
- Extend f, F, t, T to work on multiple lines.
3434
- Repeat jump by pressing f, F, t, T again. It is reset when cursor moved as a result of not jumping or timeout after idle time (duration customizable).
3535
- Highlight (after customizable delay) all possible target characters and stop it after some (customizable) idle time.
36-
- Normal, Visual, and Operator-pending (with full dot-repeat) modes are supported.
36+
- Normal, Visual, and Operator-pending (with dot-repeat as in clean Neovim) modes are supported.
3737

3838
## Installation
3939

tests/test_jump.lua

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,13 @@ T['state']['updates `mode`'] = function()
183183

184184
type_keys('d', 't', 'e')
185185
eq(get_state().mode, 'nov')
186-
child.lua('MiniJump.stop_jumping()')
186+
187+
-- Ensure dot-repeat does not update mode after the jump
188+
type_keys('V', 't', 'e')
189+
eq(get_state().mode, 'V')
190+
child.ensure_normal_mode()
191+
type_keys('.')
192+
eq(get_state().mode, 'V')
187193
end
188194

189195
T['state']['updates `jumping`'] = function()
@@ -402,7 +408,7 @@ T['Jumping with f/t/F/T']['works in Operator-pending mode'] = new_set({
402408
type_keys('d', '2', key, 'e')
403409
eq(get_lines(), { line_seq[3] })
404410

405-
-- Just typing `key` shouldn't repeat action
411+
-- Just typing `key` shouldn't repeat motion
406412
local cur_pos = get_cursor()
407413
type_keys(key)
408414
eq(get_cursor(), cur_pos)
@@ -582,6 +588,31 @@ T['Jumping with f/t/F/T']['can be dot-repeated if did not jump at first'] = func
582588
validate('dT', 1, 5, 'abcdg')
583589
end
584590

591+
T['Jumping with f/t/F/T']['inside dot-repeat is not affected by regular jumping'] = function()
592+
local validate = function(keys, key_antagonist, result)
593+
local line = '_xdxdx1x1xdxdx_'
594+
local tests_forward = key_antagonist == string.upper(key_antagonist)
595+
596+
set_lines({ line })
597+
set_cursor(1, tests_forward and 0 or (string.len(line) - 1))
598+
599+
type_keys(keys)
600+
type_keys(tests_forward and '$' or '^')
601+
type_keys(key_antagonist, '1')
602+
type_keys('.')
603+
eq(get_lines(), { result })
604+
605+
-- Ensure there is no jumping
606+
child.lua('MiniJump.stop_jumping()')
607+
child.ensure_normal_mode()
608+
end
609+
610+
validate('2dfd', 'T', 'x1x1x_')
611+
validate('2dtd', 'F', 'dx1xdx_')
612+
validate('2dFd', 't', '_x1x1x')
613+
validate('2dTd', 'f', '_xdx1xd')
614+
end
615+
585616
T['Jumping with f/t/F/T']['stops prompting for target if hit `<Esc>` or `<C-c>`'] = new_set({
586617
parametrize = {
587618
{ 'f', '<Esc>' },
@@ -878,16 +909,49 @@ T['Repeat jump with ;']['works after jump in Operator-pending mode'] = function(
878909
type_keys('d', '2f', 'e', ';')
879910
eq(get_lines(), { '3e4e5e' })
880911
eq(get_cursor(), { 1, 3 })
912+
913+
-- Should not use the latest dot repeat (like in `nvim --clean`)
914+
set_lines({ '1e2e__3e4e5e6e' })
915+
set_cursor(1, 0)
916+
917+
type_keys('d', '2f', 'e')
918+
eq(get_lines(), { '__3e4e5e6e' })
919+
eq(get_cursor(), { 1, 0 })
920+
921+
type_keys('$', 'T', '_')
922+
eq(get_lines(), { '__3e4e5e6e' })
923+
eq(get_cursor(), { 1, 2 })
924+
925+
-- - Should reuse initial Operator-pending mode state and not the latest one
926+
type_keys('.')
927+
eq(get_lines(), { '__5e6e' })
928+
eq(get_cursor(), { 1, 2 })
929+
930+
-- - The latest one should still be preserved for regular jumping
931+
type_keys(';')
932+
eq(get_lines(), { '__5e6e' })
933+
eq(get_cursor(), { 1, 1 })
881934
end
882935

883936
T['Repeat jump with ;']['works in Operator-pending mode'] = function()
884-
set_lines({ '1e2e3e4e5e' })
937+
set_lines({ '1e2e3e4e5e_' })
885938
set_cursor(1, 0)
886939

887940
-- Should repeat without asking for target
888941
type_keys('f', 'e', 'd', ';')
889-
eq(get_lines(), { '13e4e5e' })
942+
eq(get_lines(), { '13e4e5e_' })
890943
eq(get_cursor(), { 1, 1 })
944+
945+
-- Should work with dot-repeat as in `nvim --clean`, i.e. use target from the
946+
-- latest jump, as `;` is exactly for that
947+
type_keys('.')
948+
eq(get_lines(), { '14e5e_' })
949+
eq(get_cursor(), { 1, 1 })
950+
951+
type_keys('f', '_', '^')
952+
type_keys('.')
953+
eq(get_lines(), { '' })
954+
eq(get_cursor(), { 1, 0 })
891955
end
892956

893957
T['Repeat jump with ;']['works with different mapping'] = function()

0 commit comments

Comments
 (0)