This commit is contained in:
parent
9992d5cd31
commit
f48217a7fc
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
/.lux/
|
/.lux/
|
||||||
lux.lock
|
lux.lock
|
||||||
*.src.rock
|
*.src.rock
|
||||||
|
*.aider*
|
||||||
|
4
Makefile
4
Makefile
@ -3,8 +3,8 @@ PLENARY_DIR=~/.local/share/nvim/site/pack/test/opt/plenary.nvim
|
|||||||
all: lint test
|
all: lint test
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
lua-language-server --check=lua/u/ --checklevel=Hint
|
lua-language-server --check=lua/u/ --checklevel=Error
|
||||||
lux check
|
lx check
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
stylua .
|
stylua .
|
||||||
|
@ -231,8 +231,10 @@ function M.create_picker(opts) -- {{{
|
|||||||
local formatted_items = s_formatted_items:get()
|
local formatted_items = s_formatted_items:get()
|
||||||
local filter_text = vim.trim(s_filter_text:get()):lower()
|
local filter_text = vim.trim(s_filter_text:get()):lower()
|
||||||
|
|
||||||
local filter_pattern = ''
|
--- @type string
|
||||||
local use_plain_pattern = false
|
local filter_pattern
|
||||||
|
--- @type boolean
|
||||||
|
local use_plain_pattern
|
||||||
if #formatted_items > 250 and #filter_text <= 3 then
|
if #formatted_items > 250 and #filter_text <= 3 then
|
||||||
filter_pattern = filter_text
|
filter_pattern = filter_text
|
||||||
use_plain_pattern = true
|
use_plain_pattern = true
|
||||||
|
@ -38,7 +38,8 @@ function CodeWriter.from_line(line, bufnr)
|
|||||||
local expandtab = vim.api.nvim_get_option_value('expandtab', { buf = bufnr })
|
local expandtab = vim.api.nvim_get_option_value('expandtab', { buf = bufnr })
|
||||||
local shiftwidth = vim.api.nvim_get_option_value('shiftwidth', { buf = bufnr })
|
local shiftwidth = vim.api.nvim_get_option_value('shiftwidth', { buf = bufnr })
|
||||||
|
|
||||||
local indent_level = 0
|
--- @type number
|
||||||
|
local indent_level
|
||||||
local indent_str = ''
|
local indent_str = ''
|
||||||
if expandtab then
|
if expandtab then
|
||||||
while #indent_str < shiftwidth do
|
while #indent_str < shiftwidth do
|
||||||
|
@ -8,7 +8,7 @@ local __U__OpKeymapOpFunc_rhs = nil
|
|||||||
--- @type nil|fun(range: u.Range): fun():any|nil
|
--- @type nil|fun(range: u.Range): fun():any|nil
|
||||||
--- @param ty 'line'|'char'|'block'
|
--- @param ty 'line'|'char'|'block'
|
||||||
-- selene: allow(unused_variable)
|
-- selene: allow(unused_variable)
|
||||||
function __U__OpKeymapOpFunc(ty)
|
function _G.__U__OpKeymapOpFunc(ty)
|
||||||
if __U__OpKeymapOpFunc_rhs ~= nil then
|
if __U__OpKeymapOpFunc_rhs ~= nil then
|
||||||
local range = Range.from_op_func(ty)
|
local range = Range.from_op_func(ty)
|
||||||
__U__OpKeymapOpFunc_rhs(range)
|
__U__OpKeymapOpFunc_rhs(range)
|
||||||
|
@ -459,6 +459,7 @@ end
|
|||||||
function Range:text() return vim.fn.join(self:lines(), '\n') end
|
function Range:text() return vim.fn.join(self:lines(), '\n') end
|
||||||
|
|
||||||
--- @param l number
|
--- @param l number
|
||||||
|
-- luacheck: ignore
|
||||||
--- @return { line: string; idx0: { start: number; stop: number; }; lnum: number; range: fun():u.Range; text: fun():string }|nil
|
--- @return { line: string; idx0: { start: number; stop: number; }; lnum: number; range: fun():u.Range; text: fun():string }|nil
|
||||||
function Range:line(l)
|
function Range:line(l)
|
||||||
if l < 0 then l = self:line_count() + l + 1 end
|
if l < 0 then l = self:line_count() + l + 1 end
|
||||||
|
@ -5,6 +5,7 @@ local H = {}
|
|||||||
--- @alias u.renderer.Node nil | boolean | string | u.renderer.Tag
|
--- @alias u.renderer.Node nil | boolean | string | u.renderer.Tag
|
||||||
--- @alias u.renderer.Tree u.renderer.Node | u.renderer.Node[]
|
--- @alias u.renderer.Tree u.renderer.Node | u.renderer.Node[]
|
||||||
|
|
||||||
|
-- luacheck: ignore
|
||||||
--- @type table<string, fun(attributes: table<string, any>, children: u.renderer.Tree): u.renderer.Tag> & fun(name: string, attributes: table<string, any>, children: u.renderer.Tree): u.renderer.Tag>
|
--- @type table<string, fun(attributes: table<string, any>, children: u.renderer.Tree): u.renderer.Tag> & fun(name: string, attributes: table<string, any>, children: u.renderer.Tree): u.renderer.Tag>
|
||||||
M.h = setmetatable({}, {
|
M.h = setmetatable({}, {
|
||||||
__call = function(_, name, attributes, children)
|
__call = function(_, name, attributes, children)
|
||||||
@ -108,7 +109,7 @@ function Renderer.markup_to_lines(opts) -- {{{
|
|||||||
-- newlines are not controlled by array entries, do NOT output a line here:
|
-- newlines are not controlled by array entries, do NOT output a line here:
|
||||||
visit(child)
|
visit(child)
|
||||||
end
|
end
|
||||||
else
|
else -- luacheck: ignore
|
||||||
visit(node.children)
|
visit(node.children)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -178,13 +179,13 @@ function Renderer.patch_lines(bufnr, old_lines, new_lines)
|
|||||||
_set_text(lnum0, cnum0, lnum0, cnum0 + 1, { col_change.to })
|
_set_text(lnum0, cnum0, lnum0, cnum0 + 1, { col_change.to })
|
||||||
elseif col_change.kind == 'delete' then
|
elseif col_change.kind == 'delete' then
|
||||||
_set_text(lnum0, cnum0, lnum0, cnum0 + 1, {})
|
_set_text(lnum0, cnum0, lnum0, cnum0 + 1, {})
|
||||||
else
|
else -- luacheck: ignore
|
||||||
-- No change
|
-- No change
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif line_change.kind == 'delete' then
|
elseif line_change.kind == 'delete' then
|
||||||
_set_lines(lnum0, lnum0 + 1, true, {})
|
_set_lines(lnum0, lnum0 + 1, true, {})
|
||||||
else
|
else -- luacheck: ignore
|
||||||
-- No change
|
-- No change
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -464,6 +465,7 @@ function TreeBuilder:tree() return self.nodes end
|
|||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- Levenshtein utility {{{
|
-- Levenshtein utility {{{
|
||||||
|
-- luacheck: ignore
|
||||||
--- @alias LevenshteinChange<T> ({ kind: 'add'; item: T; index: number; } | { kind: 'delete'; item: T; index: number; } | { kind: 'change'; from: T; to: T; index: number; })
|
--- @alias LevenshteinChange<T> ({ kind: 'add'; item: T; index: number; } | { kind: 'delete'; item: T; index: number; } | { kind: 'change'; from: T; to: T; index: number; })
|
||||||
--- @private
|
--- @private
|
||||||
--- @generic T
|
--- @generic T
|
||||||
|
@ -202,7 +202,7 @@ end
|
|||||||
-- class ExecutionContext
|
-- class ExecutionContext
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
CURRENT_CONTEXT = nil
|
local CURRENT_CONTEXT = nil
|
||||||
|
|
||||||
--- @class u.ExecutionContext
|
--- @class u.ExecutionContext
|
||||||
--- @field signals table<u.Signal, boolean>
|
--- @field signals table<u.Signal, boolean>
|
||||||
@ -211,7 +211,7 @@ M.ExecutionContext = ExecutionContext
|
|||||||
ExecutionContext.__index = ExecutionContext
|
ExecutionContext.__index = ExecutionContext
|
||||||
|
|
||||||
--- @return u.ExecutionContext
|
--- @return u.ExecutionContext
|
||||||
function ExecutionContext:new()
|
function ExecutionContext.new()
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
signals = {},
|
signals = {},
|
||||||
subscribers = {},
|
subscribers = {},
|
||||||
@ -222,7 +222,7 @@ function ExecutionContext.current() return CURRENT_CONTEXT end
|
|||||||
|
|
||||||
--- @param fn function
|
--- @param fn function
|
||||||
--- @param ctx u.ExecutionContext
|
--- @param ctx u.ExecutionContext
|
||||||
function ExecutionContext:run(fn, ctx)
|
function ExecutionContext.run(fn, ctx)
|
||||||
local oldCtx = CURRENT_CONTEXT
|
local oldCtx = CURRENT_CONTEXT
|
||||||
CURRENT_CONTEXT = ctx
|
CURRENT_CONTEXT = ctx
|
||||||
local result
|
local result
|
||||||
@ -289,8 +289,8 @@ end
|
|||||||
--- @param fn function
|
--- @param fn function
|
||||||
--- @param name? string
|
--- @param name? string
|
||||||
function M.create_effect(fn, name)
|
function M.create_effect(fn, name)
|
||||||
local ctx = M.ExecutionContext:new()
|
local ctx = M.ExecutionContext.new()
|
||||||
M.ExecutionContext:run(fn, ctx)
|
M.ExecutionContext.run(fn, ctx)
|
||||||
return ctx:subscribe(function()
|
return ctx:subscribe(function()
|
||||||
if name and M.debug then
|
if name and M.debug then
|
||||||
local deps = vim
|
local deps = vim
|
||||||
|
@ -6,6 +6,7 @@ local M = {}
|
|||||||
|
|
||||||
--- @alias QfItem { col: number, filename: string, kind: string, lnum: number, text: string }
|
--- @alias QfItem { col: number, filename: string, kind: string, lnum: number, text: string }
|
||||||
--- @alias KeyMaps table<string, fun(): any | string> }
|
--- @alias KeyMaps table<string, fun(): any | string> }
|
||||||
|
-- luacheck: ignore
|
||||||
--- @alias CmdArgs { args: string; bang: boolean; count: number; fargs: string[]; line1: number; line2: number; mods: string; name: string; range: 0|1|2; reg: string; smods: any; info: u.Range|nil }
|
--- @alias CmdArgs { args: string; bang: boolean; count: number; fargs: string[]; line1: number; line2: number; mods: string; name: string; range: 0|1|2; reg: string; smods: any; info: u.Range|nil }
|
||||||
|
|
||||||
--- @generic T
|
--- @generic T
|
||||||
@ -34,6 +35,7 @@ end
|
|||||||
--- ```
|
--- ```
|
||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param cmd string | fun(args: CmdArgs): any
|
--- @param cmd string | fun(args: CmdArgs): any
|
||||||
|
-- luacheck: ignore
|
||||||
--- @param opts? { nargs?: 0|1|'*'|'?'|'+'; range?: boolean|'%'|number; count?: boolean|number, addr?: string; completion?: string }
|
--- @param opts? { nargs?: 0|1|'*'|'?'|'+'; range?: boolean|'%'|number; count?: boolean|number, addr?: string; completion?: string }
|
||||||
function M.ucmd(name, cmd, opts)
|
function M.ucmd(name, cmd, opts)
|
||||||
local Range = require 'u.range'
|
local Range = require 'u.range'
|
||||||
|
@ -58,66 +58,66 @@ describe('Signal', function()
|
|||||||
|
|
||||||
describe('Signal:map', function()
|
describe('Signal:map', function()
|
||||||
it('should transform the signal value', function()
|
it('should transform the signal value', function()
|
||||||
local signal = Signal:new(5)
|
local test_signal = Signal:new(5)
|
||||||
local mapped_signal = signal:map(function(value) return value * 2 end)
|
local mapped_signal = test_signal:map(function(value) return value * 2 end)
|
||||||
|
|
||||||
assert.is.equal(mapped_signal:get(), 10) -- Initial transformation
|
assert.is.equal(mapped_signal:get(), 10) -- Initial transformation
|
||||||
signal:set(10)
|
test_signal:set(10)
|
||||||
assert.is.equal(mapped_signal:get(), 20) -- Updated transformation
|
assert.is.equal(mapped_signal:get(), 20) -- Updated transformation
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('should handle empty transformations', function()
|
it('should handle empty transformations', function()
|
||||||
local signal = Signal:new(nil)
|
local test_signal = Signal:new(nil)
|
||||||
local mapped_signal = signal:map(function(value) return value or 'default' end)
|
local mapped_signal = test_signal:map(function(value) return value or 'default' end)
|
||||||
|
|
||||||
assert.is.equal(mapped_signal:get(), 'default') -- Return default
|
assert.is.equal(mapped_signal:get(), 'default') -- Return default
|
||||||
signal:set 'new value'
|
test_signal:set 'new value'
|
||||||
assert.is.equal(mapped_signal:get(), 'new value') -- Return new value
|
assert.is.equal(mapped_signal:get(), 'new value') -- Return new value
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('Signal:filter', function()
|
describe('Signal:filter', function()
|
||||||
it('should only emit values that pass the filter', function()
|
it('should only emit values that pass the filter', function()
|
||||||
local signal = Signal:new(5)
|
local test_signal = Signal:new(5)
|
||||||
local filtered_signal = signal:filter(function(value) return value > 10 end)
|
local filtered_signal = test_signal:filter(function(value) return value > 10 end)
|
||||||
|
|
||||||
assert.is.equal(filtered_signal:get(), nil) -- Initial value should not pass
|
assert.is.equal(filtered_signal:get(), nil) -- Initial value should not pass
|
||||||
signal:set(15)
|
test_signal:set(15)
|
||||||
assert.is.equal(filtered_signal:get(), 15) -- Now filtered
|
assert.is.equal(filtered_signal:get(), 15) -- Now filtered
|
||||||
signal:set(8)
|
test_signal:set(8)
|
||||||
assert.is.equal(filtered_signal:get(), 15) -- Does not pass the filter
|
assert.is.equal(filtered_signal:get(), 15) -- Does not pass the filter
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('should handle empty initial values', function()
|
it('should handle empty initial values', function()
|
||||||
local signal = Signal:new(nil)
|
local test_signal = Signal:new(nil)
|
||||||
local filtered_signal = signal:filter(function(value) return value ~= nil end)
|
local filtered_signal = test_signal:filter(function(value) return value ~= nil end)
|
||||||
|
|
||||||
assert.is.equal(filtered_signal:get(), nil) -- Should be nil
|
assert.is.equal(filtered_signal:get(), nil) -- Should be nil
|
||||||
signal:set(10)
|
test_signal:set(10)
|
||||||
assert.is.equal(filtered_signal:get(), 10) -- Should pass now
|
assert.is.equal(filtered_signal:get(), 10) -- Should pass now
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('create_memo', function()
|
describe('create_memo', function()
|
||||||
it('should compute a derived value and update when dependencies change', function()
|
it('should compute a derived value and update when dependencies change', function()
|
||||||
local signal = Signal:new(2)
|
local test_signal = Signal:new(2)
|
||||||
local memoized_signal = tracker.create_memo(function() return signal:get() * 2 end)
|
local memoized_signal = tracker.create_memo(function() return test_signal:get() * 2 end)
|
||||||
|
|
||||||
assert.is.equal(memoized_signal:get(), 4) -- Initially compute 2 * 2
|
assert.is.equal(memoized_signal:get(), 4) -- Initially compute 2 * 2
|
||||||
|
|
||||||
signal:set(3)
|
test_signal:set(3)
|
||||||
assert.is.equal(memoized_signal:get(), 6) -- Update to 3 * 2 = 6
|
assert.is.equal(memoized_signal:get(), 6) -- Update to 3 * 2 = 6
|
||||||
|
|
||||||
signal:set(5)
|
test_signal:set(5)
|
||||||
assert.is.equal(memoized_signal:get(), 10) -- Update to 5 * 2 = 10
|
assert.is.equal(memoized_signal:get(), 10) -- Update to 5 * 2 = 10
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('should not recompute if the dependencies do not change', function()
|
it('should not recompute if the dependencies do not change', function()
|
||||||
local call_count = 0
|
local call_count = 0
|
||||||
local signal = Signal:new(10)
|
local test_signal = Signal:new(10)
|
||||||
local memoized_signal = tracker.create_memo(function()
|
local memoized_signal = tracker.create_memo(function()
|
||||||
call_count = call_count + 1
|
call_count = call_count + 1
|
||||||
return signal:get() + 1
|
return test_signal:get() + 1
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert.is.equal(memoized_signal:get(), 11) -- Compute first value
|
assert.is.equal(memoized_signal:get(), 11) -- Compute first value
|
||||||
@ -126,11 +126,11 @@ describe('Signal', function()
|
|||||||
memoized_signal:get() -- Call again, should use memoized value
|
memoized_signal:get() -- Call again, should use memoized value
|
||||||
assert.is.equal(call_count, 1) -- Still should only be one call
|
assert.is.equal(call_count, 1) -- Still should only be one call
|
||||||
|
|
||||||
signal:set(10) -- Set the same value
|
test_signal:set(10) -- Set the same value
|
||||||
assert.is.equal(memoized_signal:get(), 11)
|
assert.is.equal(memoized_signal:get(), 11)
|
||||||
assert.is.equal(call_count, 2)
|
assert.is.equal(call_count, 2)
|
||||||
|
|
||||||
signal:set(20)
|
test_signal:set(20)
|
||||||
assert.is.equal(memoized_signal:get(), 21)
|
assert.is.equal(memoized_signal:get(), 21)
|
||||||
assert.is.equal(call_count, 3)
|
assert.is.equal(call_count, 3)
|
||||||
end)
|
end)
|
||||||
@ -138,31 +138,31 @@ describe('Signal', function()
|
|||||||
|
|
||||||
describe('create_effect', function()
|
describe('create_effect', function()
|
||||||
it('should track changes and execute callback', function()
|
it('should track changes and execute callback', function()
|
||||||
local signal = Signal:new(5)
|
local test_signal = Signal:new(5)
|
||||||
local call_count = 0
|
local call_count = 0
|
||||||
|
|
||||||
tracker.create_effect(function()
|
tracker.create_effect(function()
|
||||||
signal:get() -- track as a dependency
|
test_signal:get() -- track as a dependency
|
||||||
call_count = call_count + 1
|
call_count = call_count + 1
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert.is.equal(call_count, 1)
|
assert.is.equal(call_count, 1)
|
||||||
signal:set(10)
|
test_signal:set(10)
|
||||||
assert.is.equal(call_count, 2)
|
assert.is.equal(call_count, 2)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('should clean up signals and not call after dispose', function()
|
it('should clean up signals and not call after dispose', function()
|
||||||
local signal = Signal:new(5)
|
local test_signal = Signal:new(5)
|
||||||
local call_count = 0
|
local call_count = 0
|
||||||
|
|
||||||
local unsubscribe = tracker.create_effect(function()
|
local unsubscribe = tracker.create_effect(function()
|
||||||
call_count = call_count + 1
|
call_count = call_count + 1
|
||||||
return signal:get() * 2
|
return test_signal:get() * 2
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert.is.equal(call_count, 1) -- Initially calls
|
assert.is.equal(call_count, 1) -- Initially calls
|
||||||
unsubscribe() -- Unsubscribe the effect
|
unsubscribe() -- Unsubscribe the effect
|
||||||
signal:set(10) -- Update signal value
|
test_signal:set(10) -- Update signal value
|
||||||
assert.is.equal(call_count, 1) -- Callback should not be called again
|
assert.is.equal(call_count, 1) -- Callback should not be called again
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user