refactor extmark to its own file
All checks were successful
NeoVim tests / code-quality (push) Successful in 1m24s

This commit is contained in:
Jonathan Apodaca 2025-09-21 22:08:08 -06:00
parent c6b0076630
commit fc29454c55
5 changed files with 109 additions and 90 deletions

View File

@ -2,7 +2,7 @@ local Range = require 'u.range'
local Renderer = require('u.renderer').Renderer
--- @class u.Buffer
--- @field bufnr number
--- @field bufnr integer
--- @field b vim.var_accessor
--- @field bo vim.bo
--- @field renderer u.renderer.Renderer

71
lua/u/extmark.lua Normal file
View File

@ -0,0 +1,71 @@
local Pos = require 'u.pos'
---@class u.Extmark
---@field bufnr integer
---@field id integer
---@field nsid integer
local Extmark = {}
Extmark.__index = Extmark
--- @param bufnr integer
--- @param nsid integer
--- @param id integer
function Extmark.new(bufnr, nsid, id)
return setmetatable({
bufnr = bufnr,
nsid = nsid,
id = id,
}, Extmark)
end
--- @param range u.Range
--- @param nsid integer
function Extmark.from_range(range, nsid)
local r = range:to_charwise()
local stop = r.stop or r.start
local end_row = stop.lnum - 1
local end_col = stop.col
if range.mode == 'V' then
end_row = end_row + 1
end_col = 0
end
local id = vim.api.nvim_buf_set_extmark(r.start.bufnr, nsid, r.start.lnum - 1, r.start.col - 1, {
right_gravity = false,
end_right_gravity = true,
end_row = end_row,
end_col = end_col,
})
return Extmark.new(r.start.bufnr, nsid, id)
end
function Extmark:range()
local Range = require 'u.range'
local raw_extmark =
vim.api.nvim_buf_get_extmark_by_id(self.bufnr, self.nsid, self.id, { details = true })
local start_row0, start_col0, details = unpack(raw_extmark)
--- @type u.Pos
local start = Pos.from00(self.bufnr, start_row0, start_col0)
--- @type u.Pos?
local stop = details
and details.end_row
and details.end_col
and Pos.from01(self.bufnr, details.end_row, details.end_col)
local n_buf_lines = vim.api.nvim_buf_line_count(self.bufnr)
if stop and stop.lnum > n_buf_lines then
stop.lnum = n_buf_lines
stop = stop:eol()
end
if stop and stop.col == 0 then
stop.col = 1
stop = stop:next(-1)
end
return Range.new(start, stop, 'v')
end
function Extmark:delete() vim.api.nvim_buf_del_extmark(self.bufnr, self.nsid, self.id) end
return Extmark

View File

@ -7,9 +7,9 @@ local function line_text(bufnr, lnum)
end
--- @class u.Pos
--- @field bufnr number buffer number
--- @field lnum number 1-based line index
--- @field col number 1-based column index
--- @field bufnr integer buffer number
--- @field lnum integer 1-based line index
--- @field col integer 1-based column index
--- @field off number
local Pos = {}
Pos.__index = Pos
@ -31,14 +31,13 @@ end
function Pos.new(bufnr, lnum, col, off)
if bufnr == nil or bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end
if off == nil then off = 0 end
local pos = {
--- @type u.Pos
return setmetatable({
bufnr = bufnr,
lnum = lnum,
col = col,
off = off,
}
setmetatable(pos, Pos)
return pos
}, Pos)
end
--- @param bufnr? number
@ -47,6 +46,12 @@ end
--- @param off? number
function Pos.from00(bufnr, lnum0, col0, off) return Pos.new(bufnr, lnum0 + 1, col0 + 1, off) end
--- @param bufnr? number
--- @param lnum0 number 1-based
--- @param col1 number 1-based
--- @param off? number
function Pos.from01(bufnr, lnum0, col1, off) return Pos.new(bufnr, lnum0 + 1, col1, off) end
--- @param bufnr? number
--- @param lnum1 number 1-based
--- @param col0 number 1-based

View File

@ -1,14 +1,9 @@
local Extmark = require 'u.extmark'
local Pos = require 'u.pos'
local ESC = vim.api.nvim_replace_termcodes('<Esc>', true, false, true)
local NS = vim.api.nvim_create_namespace 'u.range'
---@class u.ExtmarkRange
---@field bufnr number
---@field id number
local ExtmarkRange = {}
ExtmarkRange.__index = ExtmarkRange
--- @class u.Range
--- @field start u.Pos
--- @field stop u.Pos|nil
@ -90,34 +85,6 @@ function Range.from_marks(lpos, rpos)
return Range.new(start, stop, mode)
end
--- @param bufnr number
--- @param extmark vim.api.keyset.get_extmark_item_by_id
function Range.from_extmark(bufnr, extmark)
---@type integer, integer, vim.api.keyset.extmark_details | nil
local start_row0, start_col0, details = unpack(extmark)
local start = Pos.new(bufnr, start_row0 + 1, start_col0 + 1)
local stop = details and Pos.new(bufnr, details.end_row + 1, details.end_col)
if stop ~= nil then
-- Check for invalid extmark range:
if stop < start then return Range.new(stop) end
-- Check for stop-mark past the end of the buffer:
local buf_max_lines = vim.api.nvim_buf_line_count(bufnr)
if stop.lnum > buf_max_lines then
stop.lnum = buf_max_lines
stop = stop:eol()
end
-- A stop mark at position 0 means it is at the end of the last line.
-- Move it back.
if stop.col == 0 then stop = stop:must_next(-1) end
end
return Range.new(start, stop, 'v')
end
--- @param bufnr? number
function Range.from_buf_text(bufnr)
if bufnr == nil or bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end
@ -392,22 +359,7 @@ function Range:save_to_marks(left, right)
(self:is_empty() and self.start or self.stop):save_to_mark(right)
end
function Range:save_to_extmark()
local r = self:to_charwise()
local end_row = r.stop.lnum - 1
local end_col = r.stop.col
if self.mode == 'V' then
end_row = end_row + 1
end_col = 0
end
local id = vim.api.nvim_buf_set_extmark(r.start.bufnr, NS, r.start.lnum - 1, r.start.col - 1, {
right_gravity = false,
end_right_gravity = true,
end_row = end_row,
end_col = end_col,
})
return ExtmarkRange.new(r.start.bufnr, id)
end
function Range:save_to_extmark() return Extmark.from_range(self, NS) end
function Range:set_visual_selection()
if self:is_empty() then return end
@ -703,17 +655,4 @@ function Range:highlight(group, opts)
}
end
function ExtmarkRange.new(bufnr, id) return setmetatable({ bufnr = bufnr, id = id }, ExtmarkRange) end
function ExtmarkRange:range()
return Range.from_extmark(
self.bufnr,
vim.api.nvim_buf_get_extmark_by_id(self.bufnr, NS, self.id, {
details = true,
})
)
end
function ExtmarkRange:delete() vim.api.nvim_buf_del_extmark(self.bufnr, NS, self.id) end
return Range

View File

@ -6,19 +6,19 @@ M.debug = false
-- class Signal
--------------------------------------------------------------------------------
--- @class u.Signal
--- @class u.Signal<T>
--- @field name? string
--- @field private changing boolean
--- @field private value any
--- @field private value T
--- @field private subscribers table<function, boolean>
--- @field private on_dispose_callbacks function[]
local Signal = {}
M.Signal = Signal
Signal.__index = Signal
--- @param value any
--- @param value `T`
--- @param name? string
--- @return u.Signal
--- @return u.Signal<T>
function Signal:new(value, name)
local obj = setmetatable({
name = name,
@ -30,7 +30,7 @@ function Signal:new(value, name)
return obj
end
--- @param value any
--- @param value T
function Signal:set(value)
self.value = value
@ -67,11 +67,12 @@ function Signal:set(value)
end
end
--- @param value T
function Signal:schedule_set(value)
vim.schedule(function() self:set(value) end)
end
--- @return any
--- @return T
function Signal:get()
local ctx = M.ExecutionContext.current()
if ctx then ctx:track(self) end
@ -85,8 +86,8 @@ function Signal:update(fn) self:set(fn(self.value)) end
function Signal:schedule_update(fn) self:schedule_set(fn(self.value)) end
--- @generic U
--- @param fn fun(value: T): U
--- @return u.Signal --<U>
--- @param fn fun(value: T): `U`
--- @return u.Signal<U>
function Signal:map(fn)
local mapped_signal = M.create_memo(function()
local value = self:get()
@ -95,13 +96,13 @@ function Signal:map(fn)
return mapped_signal
end
--- @return u.Signal
--- @return u.Signal<T>
function Signal:clone()
return self:map(function(x) return x end)
end
--- @param fn fun(value: T): boolean
--- @return u.Signal -- <T>
--- @return u.Signal<T>
function Signal:filter(fn)
local filtered_signal = M.create_signal(nil, self.name and self.name .. ':filtered' or nil)
local unsubscribe_from_self = self:subscribe(function(value)
@ -112,10 +113,10 @@ function Signal:filter(fn)
end
--- @param ms number
--- @return u.Signal -- <T>
--- @return u.Signal<T>
function Signal:debounce(ms)
local function set_timeout(timeout, callback)
local timer = (vim.uv or vim.loop).new_timer()
local timer = assert((vim.uv or vim.loop).new_timer(), 'could not create new timer')
timer:start(timeout, 0, function()
timer:stop()
timer:close()
@ -127,7 +128,7 @@ function Signal:debounce(ms)
local filtered = M.create_signal(self.value, self.name and self.name .. ':debounced' or nil)
--- @diagnostic disable-next-line: undefined-doc-name
--- @type { queued: { value: T, ts: number }[]; timer?: uv_timer_t; }
--- @type { queued: { value: T, ts: number }[], timer?: uv.uv_timer_t }
local state = { queued = {}, timer = nil }
local function clear_timeout()
if state.timer == nil then return end
@ -202,6 +203,7 @@ end
-- class ExecutionContext
--------------------------------------------------------------------------------
--- @type u.ExecutionContext|nil
local CURRENT_CONTEXT = nil
--- @class u.ExecutionContext
@ -262,16 +264,18 @@ end
-- Helpers
--------------------------------------------------------------------------------
--- @param value any
--- @generic T
--- @param value `T`
--- @param name? string
--- @return u.Signal
--- @return u.Signal<T>
function M.create_signal(value, name) return Signal:new(value, name) end
--- @param fn function
--- @generic T
--- @param fn fun(): `T`
--- @param name? string
--- @return u.Signal
function M.create_memo(fn, name)
--- @type u.Signal
--- @type u.Signal<T> | nil
local result
local unsubscribe = M.create_effect(function()
local value = fn()
@ -282,8 +286,8 @@ function M.create_memo(fn, name)
result = M.create_signal(value, name and ('m.s:' .. name) or nil)
end
end, name)
result:on_dispose(unsubscribe)
return result
assert(result):on_dispose(unsubscribe)
return assert(result)
end
--- @param fn function