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 local Renderer = require('u.renderer').Renderer
--- @class u.Buffer --- @class u.Buffer
--- @field bufnr number --- @field bufnr integer
--- @field b vim.var_accessor --- @field b vim.var_accessor
--- @field bo vim.bo --- @field bo vim.bo
--- @field renderer u.renderer.Renderer --- @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 end
--- @class u.Pos --- @class u.Pos
--- @field bufnr number buffer number --- @field bufnr integer buffer number
--- @field lnum number 1-based line index --- @field lnum integer 1-based line index
--- @field col number 1-based column index --- @field col integer 1-based column index
--- @field off number --- @field off number
local Pos = {} local Pos = {}
Pos.__index = Pos Pos.__index = Pos
@ -31,14 +31,13 @@ end
function Pos.new(bufnr, lnum, col, off) function Pos.new(bufnr, lnum, col, off)
if bufnr == nil or bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end if bufnr == nil or bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end
if off == nil then off = 0 end if off == nil then off = 0 end
local pos = { --- @type u.Pos
return setmetatable({
bufnr = bufnr, bufnr = bufnr,
lnum = lnum, lnum = lnum,
col = col, col = col,
off = off, off = off,
} }, Pos)
setmetatable(pos, Pos)
return pos
end end
--- @param bufnr? number --- @param bufnr? number
@ -47,6 +46,12 @@ end
--- @param off? number --- @param off? number
function Pos.from00(bufnr, lnum0, col0, off) return Pos.new(bufnr, lnum0 + 1, col0 + 1, off) end 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 bufnr? number
--- @param lnum1 number 1-based --- @param lnum1 number 1-based
--- @param col0 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 Pos = require 'u.pos'
local ESC = vim.api.nvim_replace_termcodes('<Esc>', true, false, true) local ESC = vim.api.nvim_replace_termcodes('<Esc>', true, false, true)
local NS = vim.api.nvim_create_namespace 'u.range' 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 --- @class u.Range
--- @field start u.Pos --- @field start u.Pos
--- @field stop u.Pos|nil --- @field stop u.Pos|nil
@ -90,34 +85,6 @@ function Range.from_marks(lpos, rpos)
return Range.new(start, stop, mode) return Range.new(start, stop, mode)
end 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 --- @param bufnr? number
function Range.from_buf_text(bufnr) function Range.from_buf_text(bufnr)
if bufnr == nil or bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end 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) (self:is_empty() and self.start or self.stop):save_to_mark(right)
end end
function Range:save_to_extmark() function Range:save_to_extmark() return Extmark.from_range(self, NS) end
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:set_visual_selection() function Range:set_visual_selection()
if self:is_empty() then return end if self:is_empty() then return end
@ -703,17 +655,4 @@ function Range:highlight(group, opts)
} }
end 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 return Range

View File

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