diff --git a/lua/u/buffer.lua b/lua/u/buffer.lua index f749181..b380a61 100644 --- a/lua/u/buffer.lua +++ b/lua/u/buffer.lua @@ -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 diff --git a/lua/u/extmark.lua b/lua/u/extmark.lua new file mode 100644 index 0000000..36fca25 --- /dev/null +++ b/lua/u/extmark.lua @@ -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 diff --git a/lua/u/pos.lua b/lua/u/pos.lua index e42de64..ba57352 100644 --- a/lua/u/pos.lua +++ b/lua/u/pos.lua @@ -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 diff --git a/lua/u/range.lua b/lua/u/range.lua index fbb0969..6baed0d 100644 --- a/lua/u/range.lua +++ b/lua/u/range.lua @@ -1,14 +1,9 @@ +local Extmark = require 'u.extmark' local Pos = require 'u.pos' local ESC = vim.api.nvim_replace_termcodes('', 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 diff --git a/lua/u/tracker.lua b/lua/u/tracker.lua index f3ea8ad..57a8e96 100644 --- a/lua/u/tracker.lua +++ b/lua/u/tracker.lua @@ -6,19 +6,19 @@ M.debug = false -- class Signal -------------------------------------------------------------------------------- ---- @class u.Signal +--- @class u.Signal --- @field name? string --- @field private changing boolean ---- @field private value any +--- @field private value T --- @field private subscribers table --- @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 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 -- +--- @param fn fun(value: T): `U` +--- @return u.Signal 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 function Signal:clone() return self:map(function(x) return x end) end --- @param fn fun(value: T): boolean ---- @return u.Signal -- +--- @return u.Signal 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 -- +--- @return u.Signal 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 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 | 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