4d3a32c

Branches > main > 4d3a32c > files / nvim / pack / plugins / opt / conjure / dev / python / sandbox.py

Make jump and smart_n into isolated modules :)

Grahp, 2026-02-02

Commit: 4d3a32c100c6aadc9ba95af6b94f1fa61312b460
Parent: 2bb8fe1

4 changed files:

nvim/lua/jump.lua modified (view file)

@@ -1,83 +1,77 @@-local ns = vim.api.nvim_create_namespace("dev.grahp.nvim.jump")-local utils = require("utils")+--- License: 0BSD+local ns = vim.api.nvim_create_namespace("dev.grahp.nvim-jump")  --- Name meanings: --- Jump target: Somewhere you can jump to - Union of Match and table of optional Disambiguator --- Disambiguator: A third character used to differentiate jump targets when there are multiple --- Match: Union Pos and a table of Char---- Row: The number of lines into the buffer line is? (0-based?) TODO rename?+--- Row: The number of lines into the buffer line is? (0-based?) --- Col: The number of characters into the line? (1-based?) --- Pos: table of row, col --- Char: A character used for jumping, entered by the user --- Label: A hint painted to the screen to show where jump targets are, and what disambiguator you might need to press ---- ALL CAPS- --- Blocks and reads one char of user input. --- Returns the char or nil local function read_char()   local char = vim.fn.getcharstr()-  if char == "" then-    return nil-  else-    return char-  end+  return char == "" and nil or char end -local function smart_case_find_index(s, pattern, pos)-  if not pattern:match("%u") then+local function is_all_lower_case(s)+  return not s:match("%u")+end++--- Returns a sequential table of the indecies of all instances of 'subs' in 's'.+--- If 'subs' is all lower case, 's' will be search case insensitive, otherwise it will be case sensitive+local function smart_case_find_all(s, subs)+  local pos = 1+  local acc = {}++  if is_all_lower_case(subs) then     s = s:lower()   end -  local match_start, match_end = string.find(s, pattern, pos, true)-  if match_start and match_end then-    return match_end+  while true do+    local match_pos = string.find(s, subs, pos, true)+    if not match_pos then break end+    table.insert(acc, match_pos)+    pos = match_pos + 1   end+  return acc end  local function string_get(s, i)   return s:sub(i, i) end --local function lines()-  local bufnr = vim.api.nvim_get_current_buf()-  return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)+local function get_all_buffer_lines()+  return vim.api.nvim_buf_get_lines(0, 0, -1, false) end  --- Returns all matches on the screen that start with "char". local function find_matches(char)-  local lines = lines()+  local lines = get_all_buffer_lines() -  local out = {}+  local matches = {}    local start_line = vim.fn.line("w0")   local end_line   = vim.fn.line("w$") +  --- Because jump_to() and paint_labels() are buffer-based and non screen-based when it comes to lines,+  ---  this instead gets all the lines in the buffer and filters down the lines that are actually on the screen   for row, line in ipairs(lines) do-    -- I miss filter lol     if row >= start_line and row <= end_line then-      local i = 1--      while true do-        local pos = smart_case_find_index(line, char, i)-        if not pos then-          break-        end-+      for _, pos in ipairs(smart_case_find_all(line, char)) do         local char = string_get(line, pos + 1)         if char then-          table.insert(out, {row = row,-          col = pos,-          char = char})+          table.insert(matches, {row = row, col = pos, char = char})         end--        i = pos + 1       end     end   end -  return out+  return matches end  --- Returns a table of chars to the number of times those chars appear in the entire table of matches@@ -91,17 +85,26 @@   return counts end +--- Returns a stateful iterator of the elements in 't'+local function seq_iterator(t)+  local i = 0+  return function()+    i = i + 1+    return t[i]+  end+end+ --- Takes a table of matches and adds a unique disambiguator to each match --- If a match's char is unique across the entire matches table, ---  the match will have no diambiguator, otherwise it will be picked from 'disambiguators' (a table of disambiguators) --- May return fewer matches than provided if all disambiguators are exhausted local function assign_disambiguators(matches, disambiguators)-  local next_disambiguator = utils.seq_iterator(disambiguators)+  local next_disambiguator = seq_iterator(disambiguators)    local counts = char_counts(matches)    local acc = {}-  for i, match in ipairs(matches) do+  for _, match in ipairs(matches) do     local disambiguator = next_disambiguator()     if not disambiguator then       break@@ -133,42 +136,51 @@   vim.cmd("redraw") end +--- These are in order of how easy I find them to press :p+--- Disambiguators are taken in sequence+local close_disambiguators = {+                              "a", "s", "d", "f", "j", "k", "l",+                                   "r", "u", "e", "i", "w", "o",+                                   "v", "m", "c", ",", "x", ".",++                                   "/", "p", "q", "z", "g", "h", "t", "y", "b", "n",++                                   "S", "D", "F", "J", "K", "L", ":",+                                   "R", "U", "E", "I", "W", "O",+                                        "M",      "<",      ">",++                              "?", "P", "Q", "Z", "G", "H", "T", "Y", "B", "N",+                              "'",+                              "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "[", "]", "-", "=", "`",+                              "\"",+                              "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "{", "}", "_", "+", "~",+                             }++--- Also safe disambiguators :)+ --- Returns as many matches as it can (bounded by disambiguators) local function get_jump_targets(char, disambiguators)   local matches = find_matches(char)   return assign_disambiguators(matches, disambiguators) end ---- These are in order of how easy I find them to press :p---- Disambiguators are taken in sequence-local disambiguators = {-                        "a", "s", "d", "f", "j", "k", "l",-                             "r", "u", "e", "i", "w", "o",-                             "v", "m", "c", ",", "x", ".",--                             "/", "p", "q", "z", "g", "h", "t", "y", "b", "n",--                             "S", "D", "F", "J", "K", "L", ":",-                             "R", "U", "E", "I", "W", "O",-                                  "M",      "<",      ">",--                        "?", "P", "Q", "Z", "G", "H", "T", "Y", "B", "N",-                        "'",-                        "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "[", "]", "-", "=", "`",-                        "\"",-                        "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "{", "}", "_", "+", "~",-                      }+local function jump_to(pos)+  if pos then+    vim.api.nvim_win_set_cursor(0, {pos.row, pos.col - 1})+  end+end  --- Reads the 2 or 3 inputted characters --- Returns a pos to jump to or nil-local function jump()+local function get_jump_pos(opts)+  --- Should be a merge function :p   local char = read_char()   if not char then return end -  --- TODO would be cool to do f/t jumping+  --- would be cool to do f/t jumping   --- Works across lines and uses the safe disambiguators until they run out -  local matches = get_jump_targets(char, disambiguators)+  local matches = get_jump_targets(char, opts.disambiguators)    if #matches == 0 then return end @@ -213,4 +225,28 @@   clear_labels() end +local opts = {disambiguators = close_disambiguators}++--- Reads 2 or 3 characters of user input, and jumps to the point returned by get_jump_pos()+local function jump()+  local pos = get_jump_pos(opts)+  jump_to(pos)+  return pos+end++--- Example jump bindings:+-- vim.keymap.set({"n", "v", "o"}, ";", jump)+-- vim.keymap.set({"n", "v", "o"}, "s", jump)++--- Or loaded lazily from another file:+-- vim.keymap.set({"n", "v", "o"}, "s", function() require("jump").jump() end)+-- vim.keymap.set({"n", "v", "o"}, ";", function() require("jump").jump() end)++--- Highlight Groups:++local visual = vim.api.nvim_get_hl(0, {name = "Visual"})++vim.api.nvim_set_hl(0, "Jump", visual)+vim.api.nvim_set_hl(0, "JumpDisambiguator", {bg = visual.bg, bold = true, fg = "#2ad6d6"}) -- Just a standout color :p+ return {jump = jump}

nvim/lua/keybindings.lua modified (view file)

@@ -34,56 +34,11 @@  keymap("n", "<Esc>", "<CMD>noh<CR>") -keymap("n", "<Space>u", require("plugins.oil").open)+keymap("n", "<Space>u", function() require("plugins.oil").open() end)  keymap("n", "<Space>f", function() require("fuzzy").fuzzy_find() end) -keymap({"n", "v", "o"}, ";", function()-  local pos = require("jump").jump()-  if pos then-    vim.api.nvim_win_set_cursor(0, {pos.row, pos.col - 1})-  end-  return ""-end)+keymap({"n", "v", "o"}, ";", function() require("jump").jump() end) ---- Either nil, "f/t", or "space"---- TODO should probably be per-buffer :p-local last_n_type = nil--vim.api.nvim_create_autocmd("CmdlineLeave", {-  callback = function()-    local t = vim.fn.getcmdtype()-    if t == "/" or t == "?" then-      last_n_type = "search"-    end-  end})--for _, key in ipairs({"f", "F", "t", "T"}) do-  vim.keymap.set({"n", "x", "o"}, key, function()-    last_n_type = "f/t"-    return key-  end, {expr = true, noremap = true})-end---local function smart_n(reverse)-  if last_n_type == nil then-    return ""-  elseif last_n_type == "f/t" then-    local cs = vim.fn.getcharsearch()-    if reverse then-      return cs.forward == 1 and "," or ";"-    else-      return cs.forward == 1 and ";" or ","-    end-  elseif last_n_type == "search" then-    if reverse then-      return vim.v.searchforward == 1 and "N" or "n"-    else-      return vim.v.searchforward == 1 and "n" or "N"-    end-  end-end--vim.keymap.set({"n", "v", "o"}, "n", function() return smart_n(false) end, {expr = true, noremap = true})-vim.keymap.set({"n", "v", "o"}, "N", function() return smart_n(true) end, {expr = true, noremap = true})+keymap({"n", "v", "o"}, "n", function() return require("smart_n").smart_n(false) end, {expr = true, noremap = true})+keymap({"n", "v", "o"}, "N", function() return require("smart_n").smart_n(true) end, {expr = true, noremap = true})

nvim/lua/smart_n.lua added (view file)

@@ -0,0 +1,60 @@+--- License: 0BSD++--- In case you use something else :p+FT_MOTIONS = {"f", "F", "t", "T"}++SEARCH = "search"+FT = "f/t"+--- Holds the last type of search performed+--- Either SEARCH, FT, or nil+vim.b.last_n_type = nil++vim.api.nvim_create_autocmd("CmdlineLeave", {+  callback = function()+    local t = vim.fn.getcmdtype()+    if t == "/" or t == "?" then+      vim.b.last_n_type = SEARCH+    end+  end})++for _, key in ipairs(FT_MOTIONS) do+  vim.keymap.set({"n", "x", "o"}, key, function()+    vim.b.last_n_type = FT+    return key+  end, {expr = true, noremap = true})+end++local function smart_n(reverse, not_smart)+  if vim.b.last_n_type == nil then+    return ""+  elseif vim.b.last_n_type == FT then+    local cs = vim.fn.getcharsearch()+    if reverse then+      return (not_smart and ",") or cs.forward == 1 and "," or ";"+    else+      return (not_smart and ";") or cs.forward == 1 and ";" or ","+    end+  elseif vim.b.last_n_type == SEARCH then+    if reverse then+      return (not_smart and "N") or vim.v.searchforward == 1 and "N" or "n"+    else+      return (not_smart and "n") or vim.v.searchforward == 1 and "n" or "N"+    end+  end+end++--- Goes based on search direction which is dumb but suit yourself :p+local function not_smart_n(reverse)+  return smart_n(reverse, true)+end++--- Keybindings+-- vim.keymap.set({"n", "v", "o"}, "n", function() return smart_n(false) end, {expr = true, noremap = true})+-- vim.keymap.set({"n", "v", "o"}, "N", function() return smart_n(true) end, {expr = true, noremap = true})++--- Or lazily from another file+-- vim.keymap.set({"n", "v", "o"}, "n", function() return require("smart_n").smart_n(false) end, {expr = true, noremap = true})+-- vim.keymap.set({"n", "v", "o"}, "N", function() return require("smart_n").smart_n(true) end, {expr = true, noremap = true})++return {smart_n = smart_n,+        not_smart_n = not_smart_n}

nvim/lua/utils.lua deleted (view file)

@@ -1,9 +0,0 @@-local function seq_iterator(t)-  local i = 0-  return function()-    i = i + 1-    return t[i]-  end-end--return {seq_iterator = seq_iterator}