Skip to content

Jump sometimes highlights after jumping has been stopped #2318

@abeldekat

Description

@abeldekat

Contributing guidelines

Module(s)

mini.jump

Neovim version

0.12 (!at least latest Nightly build!)

Description

There is a window-of-opportunity in MiniJump.jump

  local config = H.get_config()
  H.timers.highlight:stop()
  H.timers.highlight:start(
    -- Update highlighting immediately if any highlighting is already present
    H.is_highlighting() and 0 or config.delay.highlight,
    0,
    vim.schedule_wrap(function() H.highlight(hl_pattern) end)
  )

Preconditions:

  • As a side-effect of the jump, a function is scheduled that will trigger a BufLeave event

  • After the jump, a function is scheduled immediately that will run H.highlight(0 delay.highlight)

The problem:

On BufLeave, MiniJump.stop_jumping is called.
That will stop H.timers.highlight. However, the timer already scheduled the function.
Thus, H.highlight runs and applies the highlighting, although state.jumping is false.

This confuses the user, as the highlight indicates that the jump can be repeated by pressing f, F, t, T again.

I stumbled on this behavior when using mini.files

Reproduction

  1. Create separate 'nvim-repro' config directory:

    • '~/.config/nvim-repro/' on Unix
    • '~/AppData/Local/nvim-repro/' on Windows
  2. Inside 'nvim-repro' directory create a file named 'init.lua'.
    Populate it with the following content:

vim.pack.add({ "https://github.com/nvim-mini/mini.nvim" })

require("mini.files").setup({
  windows = { preview = true },
})
require("mini.jump").setup()
   
  1. Run NVIM_APPNAME=nvim-repro nvim (i.e. execute nvim with NVIM_APPNAME environment variable set to "nvim-repro").
    Wait for all dependencies to install.

  2. Replace this with description of interactive reproduction steps along with the behavior you observe.
    Feel free to include images/videos/etc, this helps a lot.

jump_fix_bufleave.mp4

Three files, content is same as filename

  • bar.txt
  • rejump.txt
  • star.txt

Happy flow

  • Open Neovim, type :MiniFiles.open() and enter
  • Notice that the cursor is on the first line and preview shows file 'bar.txt'
  • Type fu
  • Notice that the cursor is on the 'u' of 'rejump.txt'
  • Notice that the preview now shows 'rejump.txt'
  • Notice that there is no 'jump' highlighting
  • Type f and expect MiniJump to prompt for a target
Annotated log using `MiniMisc.log_add`
  { --> MiniJump.jump "fu" jumping directly to next line, preview file 'rejump.txt'
    desc = "Jump",
    timestamp = 4845.994182
  }, {
    desc = "Jump end",
    timestamp = 4846.599505
  }, { --> MiniFiles receives CursorMoved and schedules its H.view_track_cursor
    desc = "Scheduling H.view_track_cursor", w
    state = {
      buf = 2,
      event = "CursorMoved",
      file = "minifiles://2//home/user/.config/repro",
      group = 9,
      id = 30,
      match = "minifiles://2//home/user/.config/repro"
    },
    timestamp = 4846.863891
  }, { --> BufLeave event received from MiniFiles H.view_track_cursor
    desc = "Event received to trigger stop_jumping",
    state = {
      buf = 3,
      event = "BufLeave",
      file = "minifiles://3//home/user/.config/repro/bar.txt",
      group = 10,
      id = 18,
      match = "minifiles://3//home/user/.config/repro/bar.txt"
    },
    timestamp = 4853.303447
  }, { --> BufLeave handlers immediately calls stop_jumping, stopping H.timers.highlight
    desc = "Stop_jumping",
    timestamp = 4853.307244
  }, {
    desc = "Stop_jumping end",
    timestamp = 4853.314373
  }}
  --> End result: H.highlight is not invoked

Non happy flow

  • Open Neovim, type :MiniFiles.open() and enter
  • Notice that the cursor is on the first line and preview shows file 'bar.txt'
  • Type fr
  • Notice that the cursor is on the 'r' of 'bar.txt', still on the first line
  • Notice that the preview still shows 'bar.txt'
  • Notice that jump highlights all other 'r' in the minifiles buffer
  • Type f
  • Notice that the cursor is now on the 'r' of 'rejump.txt'
  • Notice that the preview now shows 'rejump.txt'
  • Notice that jump still highlights all other 'r' in the minifiles buffer
  • Type f and expect the cursor to move to the third line, 'star.txt'
  • However, MiniJump prompts for target
Annotated log using `MiniMisc.log_add`
  { --> MiniJump.jump "fr" jumping same line, still previews file 'bar.txt'
    desc = "Jump",
    timestamp = 10742.324058
  }, {
    desc = "Jump end",
    timestamp = 10743.036937
  }, { --> MiniFiles receives CursorMoved and schedules its H.view_track_cursor
    --> The preview is for the same file, so there will not be a `BufLeave` event
    desc = "Scheduling H.view_track_cursor",
    state = {
      buf = 2,
      event = "CursorMoved",
      file = "minifiles://2//home/user/.config/repro",
      group = 9,
      id = 30,
      match = "minifiles://2//home/user/.config/repro"
    },
    timestamp = 10743.457255
  }, { --> After 250ms delay, the timer schedules its call to H.highlight
    desc = "Scheduling H.highlight",
    state = "\\Vr",
    timestamp = 10992.505875
  }, { --> That schedule runs
    desc = "H.highlight",
    state = {
      pattern = "\\Vr",
      state = {
        backward = false,
        jumping = true,
        mode = "n",
        n_times = 1,
        target = "r",
        till = false
      }
    },
    timestamp = 10992.580637
  }, { --> Highlight applied
    desc = "H.highlight end",
    timestamp = 10992.866692
  }, { --> MiniJump.jump "f" jumping to next line, preview file 'rejump.txt'
    desc = "Jump",
    timestamp = 14499.08062
  }, {
    desc = "Jump end",
    timestamp = 14499.562205
  }, { --> MiniFiles receives CursorMoved and schedules its H.view_track_cursor
    desc = "Scheduling H.view_track_cursor",
    state = {
      buf = 2,
      event = "CursorMoved",
      file = "minifiles://2//home/user/.config/repro",
      group = 9,
      id = 30,
      match = "minifiles://2//home/user/.config/repro"
    },
    timestamp = 14499.892646
  }, { --> The timer schedules its call to H.highlight almost immediately after MiniJump.jump ends
    desc = "Scheduling H.highlight",
    state = "\\Vr",
    timestamp = 14500.432876
  }, { --> Scheduled MiniFiles H.view_track_cursor runs. MiniJump receives a BufLeave event
    desc = "Event received to trigger stop_jumping",
    state = {
      buf = 3,
      event = "BufLeave",
      file = "minifiles://3//home/user/.config/repro/bar.txt",
      group = 10,
      id = 18,
      match = "minifiles://3//home/user/.config/repro/bar.txt"
    },
    timestamp = 14506.417229
  }, { --> BufLeave handler immediately calls stop_jumping, stopping H.timers.highlight
    --> However, that timer has already **scheduled** the call to H.highlight! <--
    desc = "Stop_jumping",
    timestamp = 14506.42376
  }, {
    desc = "Stop_jumping end",
    timestamp = 14506.440574
  }, {
    desc = "H.highlight",
    state = {
      pattern = "\\Vr",
      state = {
        backward = false,
        jumping = false,
        mode = "n",
        n_times = 1,
        target = "r",
        till = false
      }
    },
    timestamp = 14506.675274
  }, { --> The scheduled call to H.highlight runs
    --> The highlight is applied, even though state.jumping is now false! <--
    desc = "H.highlight end",
    timestamp = 14506.686243
  }}

Diff log statements

mini.files
diff --git a/lua/mini/files.lua b/lua/mini/files.lua
index 1776a65c..744a70fa 100644
--- a/lua/mini/files.lua
+++ b/lua/mini/files.lua
@@ -2135,7 +2135,10 @@ H.buffer_create = function(path, mappings)
     vim.api.nvim_create_autocmd(events, { group = augroup, buffer = buf_id, desc = desc, callb
ack = callback })
   end

-  au({ 'CursorMoved', 'CursorMovedI', 'TextChangedP' }, 'Tweak cursor position', H.view_track_
cursor)
+  au({ 'CursorMoved', 'CursorMovedI', 'TextChangedP' }, 'Tweak cursor position', function(ev)
+    MiniMisc.log_add('Scheduling H.view_track_cursor', ev)
+    H.view_track_cursor(ev)
+  end)
   au({ 'TextChanged', 'TextChangedI', 'TextChangedP' }, 'Track buffer modification', H.view_tr
ack_text_change)

   -- Tweak buffer to be used nicely with other 'mini.nvim' modules
mini.jump
diff --git a/lua/mini/jump.lua b/lua/mini/jump.lua
index 0c802aa9..7df98094 100644
--- a/lua/mini/jump.lua
+++ b/lua/mini/jump.lua
@@ -179,6 +179,7 @@ MiniJump.state = {
 ---@param till __jump_till
 ---@param n_times __jump_n_times
 MiniJump.jump = function(target, backward, till, n_times)
+  MiniMisc.log_add('Jump')
   if H.is_disabled() then return end

   -- Ensure to undo "consuming a character" effect if there is no target found
@@ -218,7 +219,10 @@ MiniJump.jump = function(target, backward, till, n_times)
     -- Update highlighting immediately if any highlighting is already present
     H.is_highlighting() and 0 or config.delay.highlight,
     0,
-    vim.schedule_wrap(function() H.highlight(hl_pattern) end)
+    function()
+      MiniMisc.log_add('Scheduling H.highlight', hl_pattern)
+      vim.schedule(function() H.highlight(hl_pattern) end)
+    end
   )

   -- Start idle timer after stopping previous one
@@ -249,6 +253,7 @@ MiniJump.jump = function(target, backward, till, n_times)
     state_snapshot.jumping = true
     MiniJump.state = state_snapshot
   end
+  MiniMisc.log_add('Jump end')
 end

 --- Make smart jump
@@ -289,6 +294,7 @@ end
 --- Removes highlights (if any) and forces the next smart jump to prompt for
 --- the target. Automatically called on appropriate Neovim |events|.
 MiniJump.stop_jumping = function()
+  MiniMisc.log_add('Stop_jumping')
   H.timers.highlight:stop()
   H.timers.idle_stop:stop()

@@ -303,6 +309,7 @@ MiniJump.stop_jumping = function()

   -- Trigger relevant event only if there was jumping
   if was_jumping then H.trigger_event('MiniJumpStop') end
+  MiniMisc.log_add('Stop_jumping end')
 end

 -- Helper data ================================================================
@@ -385,7 +392,10 @@ H.create_autocommands = function()
   end

   au('CursorMoved', '*', H.on_cursormoved, 'On CursorMoved')
-  au({ 'BufLeave', 'InsertEnter' }, '*', MiniJump.stop_jumping, 'Stop jumping')
+  au({ 'BufLeave', 'InsertEnter' }, '*', function(ev)
+    MiniMisc.log_add('Event received to trigger stop_jumping', ev)
+    MiniJump.stop_jumping()
+  end, 'Stop jumping')
   au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
 end

@@ -478,10 +488,9 @@ end

 -- Highlighting ---------------------------------------------------------------
 H.highlight = function(pattern)
+  MiniMisc.log_add('H.highlight', { pattern = pattern, state = MiniJump.state })
   -- Don't do anything if already highlighting input pattern
   if H.is_highlighting(pattern) then return end

   -- Stop highlighting possible previous pattern. Needed to adjust highlighting
   -- when inside jumping but a different kind one. Example: first jump with
@@ -494,6 +503,7 @@ H.highlight = function(pattern)

   local match_id = vim.fn.matchadd('MiniJump', pattern)
   H.window_matches[vim.api.nvim_get_current_win()] = { id = match_id, pattern = pattern }
+  MiniMisc.log_add('H.highlight end')
 end

 H.unhighlight = function()
What to do after reporting an issue

After reporting the issue, it is safe (and even recommended for cleaner possible future bug reports) to remove 'nvim-repro' config from the system:

  • Delete config directory ('~/.config/nvim-repro' on Unix).
  • Delete data directory ('~/.local/share/nvim-repro' on Unix).
  • Delete state directory ('~/.local/state/nvim-repro' on Unix).

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions