412
README.md
412
README.md
@@ -1,40 +1,30 @@
|
|||||||
# u.nvim
|
# u.nvim
|
||||||
|
|
||||||
Welcome to **u.nvim** - a powerful Lua library designed to enhance your text
|
Welcome to **u.nvim** - a Lua library for text manipulation in Neovim, focusing on
|
||||||
manipulation experience in NeoVim, focusing on text-manipulation utilities.
|
range-based text operations, positions, and operator-pending mappings.
|
||||||
This includes a `Range` utility, allowing you to work efficiently with text
|
|
||||||
selections based on various conditions, as well as a declarative `Render`-er,
|
|
||||||
making coding and editing more intuitive and productive.
|
|
||||||
|
|
||||||
This is meant to be used as a **library**, not a plugin. On its own, `u.nvim`
|
This is a **single-file micro-library** meant to be vendored in your plugin or
|
||||||
does nothing. It is meant to be used by plugin authors, to make their lives
|
config. On its own, `u.nvim` does nothing. It is meant to be used by plugin
|
||||||
easier based on the variety of utilities I found I needed while growing my
|
authors to make their lives easier. To get an idea of what a plugin built on
|
||||||
NeoVim config. To get an idea of what a plugin built on top of `u.nvim` would
|
top of `u.nvim` would look like, check out the [examples/](./examples/) directory.
|
||||||
look like, check out the [examples/](./examples/) directory.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Rendering System**: a utility that can declaratively render NeoVim-specific
|
- **Range Utility**: Get context-aware selections with ease. Replace regions with
|
||||||
hyperscript into a buffer, supporting creating/managing extmarks, highlights,
|
new text. Think of it as a programmatic way to work with visual selections.
|
||||||
and key-event handling (requires NeoVim >0.11)
|
- **Position Utilities**: Work with cursor positions, marks, and extmarks.
|
||||||
- **Signals**: a simple dependency tracking system that pairs well with the
|
- **Operator Key Mapping**: Flexible key mapping that works with motions.
|
||||||
rendering utilities for creating reactive/interactive UIs in NeoVim.
|
- **Text Object Definitions**: Define custom text objects easily.
|
||||||
- **Range Utility**: Get context-aware selections with ease. Replace regions
|
- **User Command Helpers**: Create commands with range support.
|
||||||
with new text. Think of it as a programmatic way to work with visual
|
- **Repeat Utilities**: Dot-repeat support for custom operations.
|
||||||
selections (or regions of text).
|
|
||||||
- **Code Writer**: Write code with automatic indentation and formatting.
|
|
||||||
- **Operator Key Mapping**: Flexible key mapping that works with the selected
|
|
||||||
text.
|
|
||||||
- **Text and Position Utilities**: Convenient functions to manage text objects
|
|
||||||
and cursor positions.
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
This being a library, and not a proper plugin, it is recommended that you
|
This being a library, and not a proper plugin, it is recommended that you vendor
|
||||||
vendor the specific version of this library that you need, including it in your
|
the specific version of this library that you need, including it in your code.
|
||||||
code. Package managers are a developing landscape for Lua in the context of
|
Package managers are a developing landscape for Lua in the context of NeoVim.
|
||||||
NeoVim. Perhaps in the future, `lux` will eliminate the need to vendor this
|
Perhaps in the future, `lux` will eliminate the need to vendor this library in
|
||||||
library in your application code.
|
your application code.
|
||||||
|
|
||||||
#### If you are a Plugin Author
|
#### If you are a Plugin Author
|
||||||
|
|
||||||
@@ -98,201 +88,6 @@ vim.pack.add { 'https://github.com/jrop/u.nvim' }
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## Signal and Rendering Usage
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
|
|
||||||
The Signal and Rendering mechanisms are two subsystems of u.nvim, that, while
|
|
||||||
simplistic, [compose](./examples/counter.lua) [together](./examples/filetree.lua)
|
|
||||||
[powerfully](./examples/picker.lua) to create a system for interactive and
|
|
||||||
responsive user interfaces. Here is a quick example that show-cases how easy it
|
|
||||||
is to dive in to make any buffer an interactive UI:
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Example Code: counter.lua</summary>
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local tracker = require 'u.tracker'
|
|
||||||
local Buffer = require 'u.buffer'
|
|
||||||
local h = require('u.renderer').h
|
|
||||||
|
|
||||||
-- Create an buffer for the UI
|
|
||||||
vim.cmd.vnew()
|
|
||||||
local ui_buf = Buffer.current()
|
|
||||||
ui_buf:set_tmp_options()
|
|
||||||
|
|
||||||
local s_count = tracker.create_signal(0)
|
|
||||||
|
|
||||||
-- Effect: Render
|
|
||||||
-- Setup the effect for rendering the UI whenever dependencies are updated
|
|
||||||
tracker.create_effect(function()
|
|
||||||
-- Calling `Signal:get()` in an effect registers the given signal as a
|
|
||||||
-- dependency of the current effect. Whenever that signal (or any other
|
|
||||||
-- dependency) changes, the effect will rerun. In this particular case,
|
|
||||||
-- rendering the UI is an effect that depends on one signal.
|
|
||||||
local count = s_count:get()
|
|
||||||
|
|
||||||
-- Markup is hyperscript, which is just 1) text, and 2) tags (i.e.,
|
|
||||||
-- constructed with `h(...)` calls). To help organize the markup, text and
|
|
||||||
-- tags can be nested in tables at any depth. Line breaks must be specified
|
|
||||||
-- manually, with '\n'.
|
|
||||||
ui_buf:render {
|
|
||||||
'Reactive Counter Example\n',
|
|
||||||
'========================\n\n',
|
|
||||||
|
|
||||||
{ 'Counter: ', tostring(count), '\n' },
|
|
||||||
|
|
||||||
'\n',
|
|
||||||
|
|
||||||
{
|
|
||||||
h('text', {
|
|
||||||
hl = 'DiffDelete',
|
|
||||||
nmap = {
|
|
||||||
['<CR>'] = function()
|
|
||||||
-- Update the contents of the s_count signal, notifying any
|
|
||||||
-- dependencies (in this case, the render effect):
|
|
||||||
vim.schedule(function()
|
|
||||||
s_count:update(function(n) return n - 1 end)
|
|
||||||
end)
|
|
||||||
-- Also equivalent: s_count:set(s_count:get() - 1)
|
|
||||||
return ''
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
}, ' Decrement '),
|
|
||||||
' ',
|
|
||||||
h('text', {
|
|
||||||
hl = 'DiffAdd',
|
|
||||||
nmap = {
|
|
||||||
['<CR>'] = function()
|
|
||||||
-- Update the contents of the s_count signal, notifying any
|
|
||||||
-- dependencies (in this case, the render effect):
|
|
||||||
vim.schedule(function()
|
|
||||||
s_count:update(function(n) return n + 1 end)
|
|
||||||
end)
|
|
||||||
-- Also equivalent: s_count:set(s_count:get() - 1)
|
|
||||||
return ''
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
}, ' Increment '),
|
|
||||||
},
|
|
||||||
|
|
||||||
'\n',
|
|
||||||
'\n',
|
|
||||||
{ 'Press <CR> on each "button" above to increment/decrement the counter.' },
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### `u.tracker`
|
|
||||||
|
|
||||||
The `u.tracker` module provides a simple API for creating reactive variables.
|
|
||||||
These can be composed in Effects and Memos utilizing Execution Contexts that
|
|
||||||
track what signals are used by effects/memos.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local tracker = require('u.tracker')
|
|
||||||
|
|
||||||
local s_number = tracker.Signal:new(0)
|
|
||||||
-- auto-compute the double of the number each time it changes:
|
|
||||||
local s_doubled = tracker.create_memo(function() return s_number:get() * 2 end)
|
|
||||||
tracker.create_effect(function()
|
|
||||||
local n = s_doubled:get()
|
|
||||||
-- ...
|
|
||||||
-- whenever s_doubled changes, this function gets run
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: circular dependencies are **not** supported.
|
|
||||||
|
|
||||||
### `u.renderer`
|
|
||||||
|
|
||||||
The renderer library renders hyperscript into a buffer. Each render performs a
|
|
||||||
minimal set of changes in order to transform the current buffer text into the
|
|
||||||
desired state.
|
|
||||||
|
|
||||||
**Hyperscript** is just 1) _text_ 2) `<text>` tags, which can be nested in 3)
|
|
||||||
Lua tables for readability:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local h = require('u.renderer').h
|
|
||||||
-- Hyperscript can be organized into tables:
|
|
||||||
{
|
|
||||||
"Hello, ",
|
|
||||||
{
|
|
||||||
"I am ", { "a" }, " nested table.",
|
|
||||||
},
|
|
||||||
'\n', -- newlines must be explicitly specified
|
|
||||||
|
|
||||||
-- booleans/nil are ignored:
|
|
||||||
some_conditional_flag and 'This text only shows when the flag is true',
|
|
||||||
-- e.g., use the above to show newlines in lists:
|
|
||||||
idx > 1 and '\n',
|
|
||||||
|
|
||||||
-- <text> tags are specified like so:
|
|
||||||
-- h('text', attributes, children)
|
|
||||||
h('text', {}, "I am a text node."),
|
|
||||||
|
|
||||||
-- <text> tags can be highlighted:
|
|
||||||
h('text', { hl = 'Comment' }, "I am highlighted."),
|
|
||||||
|
|
||||||
-- <text> tags can respond to key events:
|
|
||||||
h('text', {
|
|
||||||
hl = 'Keyword',
|
|
||||||
nmap = {
|
|
||||||
["<CR>"] = function()
|
|
||||||
print("Hello World")
|
|
||||||
-- Return '' to swallow the event:
|
|
||||||
return ''
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
}, "I am a text node."),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Managing complex tables of hyperscript can be done more ergonomically using the
|
|
||||||
`TreeBuilder` helper class:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local TreeBuilder = require('u.renderer').TreeBuilder
|
|
||||||
|
|
||||||
-- ...
|
|
||||||
renderer:render(
|
|
||||||
TreeBuilder.new()
|
|
||||||
-- text:
|
|
||||||
:put('some text')
|
|
||||||
-- hyperscript tables:
|
|
||||||
:put({ 'some text', 'more hyperscript' })
|
|
||||||
-- hyperscript tags:
|
|
||||||
:put_h('text', { --[[attributes]] }, { --[[children]] })
|
|
||||||
-- callbacks:
|
|
||||||
--- @param tb TreeBuilder
|
|
||||||
:nest(function(tb)
|
|
||||||
tb:put('some text')
|
|
||||||
end)
|
|
||||||
:tree()
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rendering**: The renderer library provides a `render` function that takes
|
|
||||||
hyperscript in, and converts it to formatted buffer text:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local Renderer = require('u.renderer').Renderer
|
|
||||||
local renderer = Renderer:new(0 --[[buffer number]])
|
|
||||||
renderer:render {
|
|
||||||
-- ...hyperscript...
|
|
||||||
}
|
|
||||||
|
|
||||||
-- or, if you already have a buffer:
|
|
||||||
local Buffer = require('u.buffer')
|
|
||||||
local buf = Buffer.current()
|
|
||||||
buf:render {
|
|
||||||
-- ...hyperscript...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Range Usage
|
## Range Usage
|
||||||
|
|
||||||
### A note on indices
|
### A note on indices
|
||||||
@@ -327,17 +122,17 @@ interop conversions when calling `:api` functions.
|
|||||||
|
|
||||||
### 1. Creating a Range
|
### 1. Creating a Range
|
||||||
|
|
||||||
The `Range` utility is the main feature upon which most other things in this
|
The `Range` utility is the main feature of this library. Ranges can be constructed
|
||||||
library are built, aside from a few standalone utilities. Ranges can be
|
manually, or preferably, obtained based on a variety of contexts.
|
||||||
constructed manually, or preferably, obtained based on a variety of contexts.
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local Range = require 'u.range'
|
local u = require 'u'
|
||||||
local start = Pos.new(0, 1, 1) -- Line 1, first column
|
|
||||||
local stop = Pos.new(0, 3, 1) -- Line 3, first column
|
|
||||||
|
|
||||||
Range.new(start, stop, 'v') -- charwise selection
|
local start = u.Pos.new(nil, 1, 1) -- Line 1, first column
|
||||||
Range.new(start, stop, 'V') -- linewise selection
|
local stop = u.Pos.new(nil, 3, 1) -- Line 3, first column
|
||||||
|
|
||||||
|
u.Range.new(start, stop, 'v') -- charwise selection
|
||||||
|
u.Range.new(start, stop, 'V') -- linewise selection
|
||||||
```
|
```
|
||||||
|
|
||||||
This is usually not how you want to obtain a `Range`, however. Usually you want
|
This is usually not how you want to obtain a `Range`, however. Usually you want
|
||||||
@@ -345,21 +140,23 @@ to get the corresponding context of an edit operation and just "get me the
|
|||||||
current Range that represents this context".
|
current Range that represents this context".
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
|
local u = require 'u'
|
||||||
|
|
||||||
-- get the first line in a buffer:
|
-- get the first line in a buffer:
|
||||||
Range.from_line(bufnr, 1)
|
u.Range.from_line(bufnr, 1)
|
||||||
|
|
||||||
-- Text Objects (any text object valid in your configuration is supported):
|
-- Text Objects (any text object valid in your configuration is supported):
|
||||||
-- get the word the cursor is on:
|
-- get the word the cursor is on:
|
||||||
Range.from_motion('iw')
|
u.Range.from_motion('iw')
|
||||||
-- get the WORD the cursor is on:
|
-- get the WORD the cursor is on:
|
||||||
Range.from_motion('iW')
|
u.Range.from_motion('iW')
|
||||||
-- get the "..." the cursor is within:
|
-- get the "..." the cursor is within:
|
||||||
Range.from_motion('a"')
|
u.Range.from_motion('a"')
|
||||||
|
|
||||||
-- Get the currently visually selected text:
|
-- Get the currently visually selected text:
|
||||||
-- NOTE: this does NOT work within certain contexts; more specialized utilities
|
-- NOTE: this does NOT work within certain contexts; more specialized utilities
|
||||||
-- are more appropriate in certain circumstances
|
-- are more appropriate in certain circumstances
|
||||||
Range.from_vtext()
|
u.Range.from_vtext()
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Get the operated on text obtained from a motion:
|
-- Get the operated on text obtained from a motion:
|
||||||
@@ -367,7 +164,7 @@ Range.from_vtext()
|
|||||||
--
|
--
|
||||||
--- @param ty 'char'|'line'|'block'
|
--- @param ty 'char'|'line'|'block'
|
||||||
function MyOpFunc(ty)
|
function MyOpFunc(ty)
|
||||||
local range = Range.from_op_func(ty)
|
local range = u.Range.from_op_func(ty)
|
||||||
-- do something with the range
|
-- do something with the range
|
||||||
end
|
end
|
||||||
-- Try invoking this with: `<Leader>toaw`, and the current word will be the
|
-- Try invoking this with: `<Leader>toaw`, and the current word will be the
|
||||||
@@ -383,7 +180,7 @@ end, { expr = true })
|
|||||||
-- When executing commands in a visual context, getting the selected text has
|
-- When executing commands in a visual context, getting the selected text has
|
||||||
-- to be done differently:
|
-- to be done differently:
|
||||||
vim.api.nvim_create_user_command('MyCmd', function(args)
|
vim.api.nvim_create_user_command('MyCmd', function(args)
|
||||||
local range = Range.from_cmd_args(args)
|
local range = u.Range.from_cmd_args(args)
|
||||||
if range == nil then
|
if range == nil then
|
||||||
-- the command was executed in normal mode
|
-- the command was executed in normal mode
|
||||||
else
|
else
|
||||||
@@ -398,7 +195,7 @@ range once you have one? Plenty, it turns out!
|
|||||||
```lua
|
```lua
|
||||||
local range = ...
|
local range = ...
|
||||||
range:lines() -- get the lines in the range's region
|
range:lines() -- get the lines in the range's region
|
||||||
range:text() -- get the text (i.e., string) in the range's region
|
range:text() -- get the text (i.e., string) in the range's region
|
||||||
range:line(1) -- get the first line within this range
|
range:line(1) -- get the first line within this range
|
||||||
range:line(-1) -- get the last line within this range
|
range:line(-1) -- get the last line within this range
|
||||||
-- replace with new contents:
|
-- replace with new contents:
|
||||||
@@ -416,68 +213,125 @@ range:replace(nil)
|
|||||||
Define custom (dot-repeatable) key mappings for text objects:
|
Define custom (dot-repeatable) key mappings for text objects:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local opkeymap = require 'u.opkeymap'
|
local u = require 'u'
|
||||||
|
|
||||||
-- invoke this function by typing, for example, `<leader>riw`:
|
-- invoke this function by typing, for example, `<leader>riw`:
|
||||||
-- `range` will contain the bounds of the motion `iw`.
|
-- `range` will contain the bounds of the motion `iw`.
|
||||||
opkeymap('n', '<leader>r', function(range)
|
u.opkeymap('n', '<leader>r', function(range)
|
||||||
print(range:text()) -- Prints the text within the selected range
|
print(range:text()) -- Prints the text within the selected range
|
||||||
end)
|
end)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Working with Code Writer
|
### 3. Utility Functions
|
||||||
|
|
||||||
To write code with indentation, use the `CodeWriter` class:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local CodeWriter = require 'u.codewriter'
|
|
||||||
local cw = CodeWriter.new()
|
|
||||||
cw:write('{')
|
|
||||||
cw:indent(function(innerCW)
|
|
||||||
innerCW:write('x: 123')
|
|
||||||
end)
|
|
||||||
cw:write('}')
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Utility Functions
|
|
||||||
|
|
||||||
#### Custom Text Objects
|
#### Custom Text Objects
|
||||||
|
|
||||||
Simply by returning a `Range` or a `Pos`, you can easily and quickly define
|
Simply by returning a `Range`, you can easily define your own text objects:
|
||||||
your own text objects:
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local txtobj = require 'u.txtobj'
|
local u = require 'u'
|
||||||
local Range = require 'u.range'
|
|
||||||
|
|
||||||
-- Select whole file:
|
-- Select whole file:
|
||||||
txtobj.define('ag', function()
|
u.define_txtobj('ag', function()
|
||||||
return Range.from_buf_text()
|
return u.Range.from_buf_text()
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Select content inside nearest quotes:
|
||||||
|
u.define_txtobj('iq', function()
|
||||||
|
return u.Range.find_nearest_quotes()
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Select content inside nearest brackets:
|
||||||
|
u.define_txtobj('ib', function()
|
||||||
|
return u.Range.find_nearest_brackets()
|
||||||
end)
|
end)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Buffer Management
|
#### User Commands with Range Support
|
||||||
|
|
||||||
Access and manipulate buffers easily:
|
Create user commands that work with visual selections:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local Buffer = require 'u.buffer'
|
local u = require 'u'
|
||||||
local buf = Buffer.current()
|
|
||||||
buf.b.<option> -- get buffer-local variables
|
u.ucmd('MyCmd', function(args)
|
||||||
buf.b.<option> = ... -- set buffer-local variables
|
if args.info then
|
||||||
buf.bo.<option> -- get buffer options
|
-- args.info is a Range representing the selection
|
||||||
buf.bo.<option> = ... -- set buffer options
|
print('Selected text:', args.info:text())
|
||||||
buf:line_count() -- the number of lines in the current buffer
|
else
|
||||||
buf:all() -- returns a Range representing the entire buffer
|
-- No range provided
|
||||||
buf:is_empty() -- returns true if the buffer has no text
|
print('No selection')
|
||||||
buf:append_line '...'
|
end
|
||||||
buf:line(1) -- returns a Range representing the first line in the buffer
|
end, { range = true })
|
||||||
buf:line(-1) -- returns a Range representing the last line in the buffer
|
|
||||||
buf:lines(1, 2) -- returns a Range representing the first two lines in the buffer
|
|
||||||
buf:lines(2, -2) -- returns a Range representing all but the first and last lines of a buffer
|
|
||||||
buf:txtobj('iw') -- returns a Range representing the text object 'iw' in the give buffer
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Repeat Utilities
|
||||||
|
|
||||||
|
Enable dot-repeat for custom operations:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local u = require 'u'
|
||||||
|
|
||||||
|
-- Call this in your plugin's setup:
|
||||||
|
u.repeat_.setup()
|
||||||
|
|
||||||
|
-- Then use in your operations:
|
||||||
|
u.repeat_.run_repeatable(function()
|
||||||
|
-- Your mutation here
|
||||||
|
-- This will be repeatable with '.'
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### `u.Pos`
|
||||||
|
|
||||||
|
Position class representing a location in a buffer.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local pos = u.Pos.new(bufnr, lnum, col, off)
|
||||||
|
pos:next(1) -- next position (forward)
|
||||||
|
pos:next(-1) -- previous position (backward)
|
||||||
|
pos:char() -- character at position
|
||||||
|
pos:line() -- line text
|
||||||
|
pos:eol() -- end of line position
|
||||||
|
pos:save_to_cursor() -- move cursor to position
|
||||||
|
```
|
||||||
|
|
||||||
|
### `u.Range`
|
||||||
|
|
||||||
|
Range class representing a text region.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local range = u.Range.new(start, stop, mode)
|
||||||
|
range:text() -- get text content
|
||||||
|
range:lines() -- get lines as array
|
||||||
|
range:replace(text) -- replace with new text
|
||||||
|
range:contains(pos) -- check if position is within range
|
||||||
|
range:shrink(n) -- shrink range by n characters from each side
|
||||||
|
range:highlight('Search') -- highlight range temporarily
|
||||||
|
```
|
||||||
|
|
||||||
|
### `u.opkeymap(mode, lhs, rhs, opts)`
|
||||||
|
|
||||||
|
Create operator-pending keymaps.
|
||||||
|
|
||||||
|
### `u.define_txtobj(key_seq, fn, opts)`
|
||||||
|
|
||||||
|
Define custom text objects.
|
||||||
|
|
||||||
|
### `u.ucmd(name, cmd, opts)`
|
||||||
|
|
||||||
|
Create user commands with enhanced range support.
|
||||||
|
|
||||||
|
### `u.create_delegated_cmd_args(args)`
|
||||||
|
|
||||||
|
Create arguments for delegating between commands.
|
||||||
|
|
||||||
|
### `u.repeat_`
|
||||||
|
|
||||||
|
Module for dot-repeat support.
|
||||||
|
|
||||||
## License (MIT)
|
## License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2024 jrapodaca@gmail.com
|
Copyright (c) 2024 jrapodaca@gmail.com
|
||||||
|
|||||||
Reference in New Issue
Block a user