This commit is contained in:
parent
ad2e579d1d
commit
24dc69f2ac
120
examples/form.lua
Normal file
120
examples/form.lua
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
-- 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'
|
||||||
|
|
||||||
|
-- Utility to trim brackets from strings:
|
||||||
|
local function trimb(s) return (s:gsub('^%[(.*)%]$', '%1')) end
|
||||||
|
|
||||||
|
-- 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 = trimb(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),
|
||||||
|
},
|
||||||
|
'\n',
|
||||||
|
{
|
||||||
|
'Age: ',
|
||||||
|
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, "', trimb(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)
|
@ -5,7 +5,7 @@ local H = {}
|
|||||||
|
|
||||||
--- @alias u.renderer.TagEventHandler fun(tag: u.renderer.Tag, mode: string, lhs: string): string
|
--- @alias u.renderer.TagEventHandler fun(tag: u.renderer.Tag, mode: string, lhs: string): string
|
||||||
|
|
||||||
--- @alias u.renderer.TagAttributes { [string]?: unknown; imap?: table<string, u.renderer.TagEventHandler>; nmap?: table<string, u.renderer.TagEventHandler>; vmap?: table<string, u.renderer.TagEventHandler>; xmap?: table<string, u.renderer.TagEventHandler>; omap?: table<string, u.renderer.TagEventHandler> }
|
--- @alias u.renderer.TagAttributes { [string]?: unknown; imap?: table<string, u.renderer.TagEventHandler>; nmap?: table<string, u.renderer.TagEventHandler>; vmap?: table<string, u.renderer.TagEventHandler>; xmap?: table<string, u.renderer.TagEventHandler>; omap?: table<string, u.renderer.TagEventHandler>, on_change?: fun(text: string): unknown }
|
||||||
|
|
||||||
--- @class u.renderer.Tag
|
--- @class u.renderer.Tag
|
||||||
--- @field kind 'tag'
|
--- @field kind 'tag'
|
||||||
@ -296,10 +296,15 @@ function Renderer:_reconcile() -- {{{
|
|||||||
|
|
||||||
--
|
--
|
||||||
-- Step 2: reconcile extmarks:
|
-- Step 2: reconcile extmarks:
|
||||||
|
-- You may be tempted to try to keep track of which extmarks are needed, and
|
||||||
|
-- only delete those that are not needed. However, each time a tree is
|
||||||
|
-- rendered, brand new extmarks are created. For simplicity, it is better to
|
||||||
|
-- just delete all extmarks, and recreate them.
|
||||||
--
|
--
|
||||||
|
|
||||||
-- Clear current extmarks:
|
-- Clear current extmarks:
|
||||||
vim.api.nvim_buf_clear_namespace(self.bufnr, self.ns, 0, -1)
|
vim.api.nvim_buf_clear_namespace(self.bufnr, self.ns, 0, -1)
|
||||||
|
|
||||||
-- Set current extmarks:
|
-- Set current extmarks:
|
||||||
for _, extmark in ipairs(self.curr.extmarks) do
|
for _, extmark in ipairs(self.curr.extmarks) do
|
||||||
extmark.id = vim.api.nvim_buf_set_extmark(
|
extmark.id = vim.api.nvim_buf_set_extmark(
|
||||||
@ -361,10 +366,9 @@ function Renderer:_on_text_changed()
|
|||||||
for _, pos_info in ipairs(pos_infos) do
|
for _, pos_info in ipairs(pos_infos) do
|
||||||
local extmark_inf = pos_info.extmark
|
local extmark_inf = pos_info.extmark
|
||||||
local tag = pos_info.tag
|
local tag = pos_info.tag
|
||||||
if tag.attributes.signal and getmetatable(tag.attributes.signal) == Signal then
|
|
||||||
--- @type u.Signal
|
|
||||||
local signal = tag.attributes.signal
|
|
||||||
|
|
||||||
|
local on_change = tag.attributes.on_change
|
||||||
|
if on_change and type(on_change) == 'function' then
|
||||||
local extmark =
|
local extmark =
|
||||||
vim.api.nvim_buf_get_extmark_by_id(self.bufnr, self.ns, extmark_inf.id, { details = true })
|
vim.api.nvim_buf_get_extmark_by_id(self.bufnr, self.ns, extmark_inf.id, { details = true })
|
||||||
|
|
||||||
@ -381,7 +385,7 @@ function Renderer:_on_text_changed()
|
|||||||
{ type = 'v' }
|
{ type = 'v' }
|
||||||
)
|
)
|
||||||
local text = table.concat(lines, '\n')
|
local text = table.concat(lines, '\n')
|
||||||
if text ~= signal:get() then signal:schedule_set(text) end
|
on_change(text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user