range: extmarks/tsquery; renderer: text-change
This commit is contained in:
@@ -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
117
examples/form.lua
Normal 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)
|
||||
@@ -14,7 +14,7 @@ local ICONS = {
|
||||
}
|
||||
local DEFAULT_ICON = { text = '', group = 'DiagnosticSignOk' }
|
||||
|
||||
--- @alias Notification {
|
||||
--- @alias u.examples.Notification {
|
||||
--- kind: number;
|
||||
--- id: number;
|
||||
--- text: string;
|
||||
@@ -30,7 +30,7 @@ local s_notifications = s_notifications_raw:debounce(50)
|
||||
|
||||
-- Render effect:
|
||||
tracker.create_effect(function()
|
||||
--- @type Notification[]
|
||||
--- @type u.examples.Notification[]
|
||||
local notifs = s_notifications:get()
|
||||
|
||||
if #notifs == 0 then
|
||||
@@ -99,14 +99,14 @@ local function my_notify(msg, level, opts)
|
||||
|
||||
local id = math.random(math.huge)
|
||||
|
||||
--- @param notifs Notification[]
|
||||
--- @param notifs u.examples.Notification[]
|
||||
s_notifications_raw:schedule_update(function(notifs)
|
||||
table.insert(notifs, { kind = level, id = id, text = msg })
|
||||
return notifs
|
||||
end)
|
||||
|
||||
vim.defer_fn(function()
|
||||
--- @param notifs 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
|
||||
|
||||
@@ -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 -- }}}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user