range: extmarks/tsquery; renderer: text-change
All checks were successful
NeoVim tests / code-quality (push) Successful in 1m18s

This commit is contained in:
2025-06-11 20:04:46 -06:00
parent 12945a4cdf
commit 72b6886838
25 changed files with 1371 additions and 224 deletions

View File

@@ -1,5 +1,5 @@
local tracker = require 'u.tracker'
local Buffer = require 'u.buffer'
local tracker = require 'u.tracker'
local h = require('u.renderer').h
-- Create an buffer for the UI

View File

@@ -10,10 +10,10 @@
-- change on the underlying filesystem.
--------------------------------------------------------------------------------
--- @alias FsDir { kind: 'dir'; path: string; expanded: boolean; children: FsNode[] }
--- @alias FsFile { kind: 'file'; path: string }
--- @alias FsNode FsDir | FsFile
--- @alias ShowOpts { root_path?: string, width?: number, focus_path?: string }
--- @alias u.examples.FsDir { kind: 'dir'; path: string; expanded: boolean; children: u.examples.FsNode[] }
--- @alias u.examples.FsFile { kind: 'file'; path: string }
--- @alias u.examples.FsNode u.examples.FsDir | u.examples.FsFile
--- @alias u.examples.ShowOpts { root_path?: string, width?: number, focus_path?: string }
local Buffer = require 'u.buffer'
local Renderer = require('u.renderer').Renderer
@@ -58,13 +58,13 @@ function H.relative(path, base)
end
--- @param root_path string
--- @return { tree: FsDir; path_to_node: table<string, FsNode> }
--- @return { tree: u.examples.FsDir; path_to_node: table<string, u.examples.FsNode> }
function H.get_tree_inf(root_path)
logger:info { 'get_tree_inf', root_path }
--- @type table<string, FsNode>
--- @type table<string, u.examples.FsNode>
local path_to_node = {}
--- @type FsDir
--- @type u.examples.FsDir
local tree = {
kind = 'dir',
path = H.normalize(root_path or '.'),
@@ -77,8 +77,8 @@ function H.get_tree_inf(root_path)
return { tree = tree, path_to_node = path_to_node }
end
--- @param tree FsDir
--- @param path_to_node table<string, FsNode>
--- @param tree u.examples.FsDir
--- @param path_to_node table<string, u.examples.FsNode>
function H.populate_dir_children(tree, path_to_node)
tree.children = {}
@@ -135,7 +135,7 @@ local function _render_in_buffer(opts)
local parts = H.split_path(H.relative(focused_path, tree_inf.tree.path))
local path_to_node = tree_inf.path_to_node
--- @param node FsDir
--- @param node u.examples.FsDir
--- @param child_names string[]
local function expand_to(node, child_names)
if #child_names == 0 then return end
@@ -310,7 +310,7 @@ local function _render_in_buffer(opts)
--
local renderer = Renderer.new(opts.bufnr)
tracker.create_effect(function()
--- @type { tree: FsDir; path_to_node: table<string, FsNode> }
--- @type { tree: u.examples.FsDir; path_to_node: table<string, u.examples.FsNode> }
local tree_inf = s_tree_inf:get()
local tree = tree_inf.tree
@@ -329,7 +329,7 @@ local function _render_in_buffer(opts)
--- Since the filesystem is a recursive tree of nodes, we need to
--- recursively render each node. This function does just that:
--- @param node FsNode
--- @param node u.examples.FsNode
--- @param level number
local function render_node(node, level)
local name = vim.fs.basename(node.path)
@@ -414,7 +414,7 @@ end
local current_inf = nil
--- Show the filetree:
--- @param opts? ShowOpts
--- @param opts? u.examples.ShowOpts
function M.show(opts)
if current_inf ~= nil then return current_inf.controller end
opts = opts or {}
@@ -456,7 +456,7 @@ function M.hide()
end
--- Toggle the filetree:
--- @param opts? ShowOpts
--- @param opts? u.examples.ShowOpts
function M.toggle(opts)
if current_inf == nil then
M.show(opts)

117
examples/form.lua Normal file
View File

@@ -0,0 +1,117 @@
-- form.lua:
--
-- This is a runnable example of a form. Open this file in Neovim, and execute
-- `:luafile %` to run it. It will create a new buffer to the side, and render
-- an interactive form. Edit the "inputs" between the `[...]` brackets, and
-- watch the buffer react immediately to your changes.
--
local Renderer = require('u.renderer').Renderer
local h = require('u.renderer').h
local tracker = require 'u.tracker'
-- Create a new, temporary, buffer to the side:
vim.cmd.vnew()
vim.bo.buftype = 'nofile'
vim.bo.bufhidden = 'wipe'
vim.bo.buflisted = false
local renderer = Renderer.new()
-- Create two signals:
local s_name = tracker.create_signal 'whoever-you-are'
local s_age = tracker.create_signal 'ideally-a-number'
-- We can create derived information from the signals above. Say we want to do
-- some validation on the input for `age`: we can do that with a memo:
local s_age_info = tracker.create_memo(function()
local age_raw = s_age:get()
local age_digits = age_raw:match '^%s*(%d+)%s*$'
local age_n = age_digits and tonumber(age_digits) or nil
return {
type = age_n and 'number' or 'string',
raw = age_raw,
n = age_n,
n1 = age_n and age_n + 1 or nil,
}
end)
-- This is the render effect that depends on the signals created above. This
-- will re-run every time one of the signals changes.
tracker.create_effect(function()
local name = s_name:get()
local age = s_age:get()
local age_info = s_age_info:get()
-- Each time the signals change, we re-render the buffer:
renderer:render {
h.Type({}, '# Form Example'),
'\n\n',
-- We can also listen for when specific locations in the buffer change, on
-- a tag-by-tag basis. This gives us two-way data-binding between the
-- buffer and the signals.
{
'Name: ',
h.Structure({
on_change = function(text) s_name:set(text) end,
}, name),
},
{
'\nAge: ',
h.Structure({
on_change = function(text) s_age:set(text) end,
}, age),
},
'\n\n',
-- Show the values of the signals here, too, so that we can see the
-- reactivity in action. If you change the values in the tags above, you
-- can see the changes reflected here immediately.
{ 'Hello, "', name, '"!' },
--
-- A more complex example: we can do much more complex rendering, based on
-- the state. For example, if you type different values into the `age`
-- field, you can see not only the displayed information change, but also
-- the color of the highlights in this section will adapt to the type of
-- information that has been detected.
--
-- If string input is detected, values below are shown in the
-- `String`/`ErrorMsg` highlight groups.
--
-- If number input is detected, values below are shown in the `Number`
-- highlight group.
--
-- If a valid number is entered, then this section also displays how old
-- you willl be next year (`n + 1`).
--
'\n\n',
h.Type({}, '## Computed Information (derived from `age`)'),
'\n\n',
{
'Type: ',
h('text', {
hl = age_info.type == 'number' and 'Number' or 'String',
}, age_info.type),
},
{ '\nRaw input: ', h.String({}, '"' .. age_info.raw .. '"') },
{
'\nCurrent age: ',
age_info.n
-- Show the age:
and h.Number({}, tostring(age_info.n))
-- Show an error-placeholder if the age is invalid:
or h.ErrorMsg({}, '(?)'),
},
-- This part is shown conditionally, i.e., only if the age next year can be
-- computed:
age_info.n1
and {
'\nAge next year: ',
h.Number({}, tostring(age_info.n1)),
},
}
end)

View File

@@ -22,7 +22,7 @@ vim.api.nvim_create_autocmd('VimResized', {
end,
})
--- @alias u.example.Notification {
--- @alias u.examples.Notification {
--- kind: number;
--- id: number;
--- text: string;
@@ -31,7 +31,7 @@ vim.api.nvim_create_autocmd('VimResized', {
local M = {}
--- @type { win: integer, buf: integer, renderer: u.Renderer } | nil
--- @type { win: integer, buf: integer, renderer: u.renderer.Renderer } | nil
local notifs_w
local s_notifications_raw = tracker.create_signal {}
@@ -39,7 +39,7 @@ local s_notifications = s_notifications_raw:debounce(50)
-- Render effect:
tracker.create_effect(function()
--- @type u.example.Notification[]
--- @type u.examples.Notification[]
local notifs = s_notifications:get()
--- @type { width: integer, height: integer }
local editor_size = S_EDITOR_DIMENSIONS:get()
@@ -105,7 +105,7 @@ end)
--- @param id number
local function _delete_notif(id)
--- @param notifs u.example.Notification[]
--- @param notifs u.examples.Notification[]
s_notifications_raw:schedule_update(function(notifs)
for i, notif in ipairs(notifs) do
if notif.id == id then
@@ -130,7 +130,7 @@ function M.notify(msg, level, opts)
opts = opts or {}
local id = opts.id or math.random(999999999)
--- @type u.example.Notification?
--- @type u.examples.Notification?
local notif = vim.iter(s_notifications_raw:get()):find(function(n) return n.id == id end)
if not notif then
-- Create a new notification (maybe):
@@ -145,7 +145,7 @@ function M.notify(msg, level, opts)
text = msg,
timer = timer,
}
--- @param notifs u.example.Notification[]
--- @param notifs u.examples.Notification[]
s_notifications_raw:schedule_update(function(notifs)
table.insert(notifs, notif)
return notifs

View File

@@ -1,5 +1,5 @@
local utils = require 'u.utils'
local Buffer = require 'u.buffer'
local utils = require 'u.utils'
local Renderer = require('u.renderer').Renderer
local h = require('u.renderer').h
local TreeBuilder = require('u.renderer').TreeBuilder
@@ -44,7 +44,7 @@ local function shallow_copy_arr(arr) return vim.iter(arr):totable() end
-- shortest portion of this function.
--------------------------------------------------------------------------------
--- @alias SelectController {
--- @alias u.examples.SelectController {
--- get_items: fun(): T[];
--- set_items: fun(items: T[]);
--- set_filter_text: fun(filter_text: string);
@@ -53,17 +53,17 @@ local function shallow_copy_arr(arr) return vim.iter(arr):totable() end
--- set_selected_indices: fun(indicies: number[], ephemeral?: boolean);
--- close: fun();
--- }
--- @alias SelectOpts<T> {
--- @alias u.examples.SelectOpts<T> {
--- items: `T`[];
--- multi?: boolean;
--- format_item?: fun(item: T): Tree;
--- format_item?: fun(item: T): u.renderer.Tree;
--- on_finish?: fun(items: T[], indicies: number[]);
--- on_selection_changed?: fun(items: T[], indicies: number[]);
--- mappings?: table<string, fun(select: SelectController)>;
--- mappings?: table<string, fun(select: u.examples.SelectController)>;
--- }
--- @generic T
--- @param opts SelectOpts<T>
--- @param opts u.examples.SelectOpts<T>
function M.create_picker(opts) -- {{{
local is_in_insert_mode = vim.api.nvim_get_mode().mode:sub(1, 1) == 'i'
local stopinsert = not is_in_insert_mode
@@ -557,7 +557,7 @@ function M.create_picker(opts) -- {{{
return safe_run(function() H.finish(true) end)
end
return controller --[[@as SelectController]]
return controller --[[@as u.examples.SelectController]]
end -- }}}
--------------------------------------------------------------------------------

View File

@@ -1,6 +1,6 @@
local vim_repeat = require 'u.repeat'
local CodeWriter = require 'u.codewriter'
local Range = require 'u.range'
local vim_repeat = require 'u.repeat'
local M = {}

View File

@@ -1,7 +1,7 @@
local vim_repeat = require 'u.repeat'
local Range = require 'u.range'
local Buffer = require 'u.buffer'
local CodeWriter = require 'u.codewriter'
local Range = require 'u.range'
local vim_repeat = require 'u.repeat'
local M = {}

View File

@@ -1,7 +1,7 @@
local txtobj = require 'u.txtobj'
local Buffer = require 'u.buffer'
local Pos = require 'u.pos'
local Range = require 'u.range'
local Buffer = require 'u.buffer'
local txtobj = require 'u.txtobj'
local M = {}