Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9ea5b0658 | |||
| 88b7a11efa | |||
| b473ac3923 |
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -6,7 +6,3 @@
|
|||||||
path = library/luv
|
path = library/luv
|
||||||
url = https://github.com/LuaCATS/luv
|
url = https://github.com/LuaCATS/luv
|
||||||
branch = main
|
branch = main
|
||||||
[submodule "nvimv"]
|
|
||||||
path = nvimv
|
|
||||||
url = https://github.com/jrop/nvimv
|
|
||||||
branch = main
|
|
||||||
|
|||||||
34
AGENTS.md
Normal file
34
AGENTS.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Project
|
||||||
|
|
||||||
|
Single-file Neovim Lua micro-library (`lua/u.lua`) for range-based text operations, positions, operator-pending mappings, and text objects. Not a plugin — meant to be vendored by other plugins.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
All tasks use `mise`:
|
||||||
|
|
||||||
|
- `mise run fmt` — format (stylua)
|
||||||
|
- `mise run fmt:check` — check formatting
|
||||||
|
- `mise run lint` — type-check with `emmylua_check` (ignores `.prefix/`)
|
||||||
|
- `mise run test` — run busted against Neovim 0.12.1 (includes `test:prepare`)
|
||||||
|
- `mise run test:all` — test against 0.11.5, 0.12.1, and nightly
|
||||||
|
- `mise run test:coverage` — test with luacov (only covers `lua/u$`)
|
||||||
|
- `mise run ci` — `fmt:check` → `lint` → `test:all`
|
||||||
|
|
||||||
|
Run a single spec file: `busted spec/u_spec.lua` (after `mise run test:prepare`).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- **`lua/u.lua`** — the entire library (~1300 lines): `Pos`, `Range`, `opkeymap`, `define_txtobj`, `ucmd`, `repeat_`
|
||||||
|
- **`spec/u_spec.lua`** — all tests in one file
|
||||||
|
- **`library/`** — type stubs for busted/luv (stylua-ignored, used by emmylua)
|
||||||
|
- **`.prefix/`** — neovim version installs managed by `nvimv` (git-ignored, emmylua-ignored)
|
||||||
|
- **`examples/`** — usage examples (surround, splitjoin, text-objects, matcher)
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- **1-based indexing** everywhere (v2+). `Pos.from00()` / `Range.from00()` convert from 0-based Neovim API values.
|
||||||
|
- Stylua: LuaJIT syntax, single quotes, no call parens, 2-space indent, sort requires, 100 col width
|
||||||
|
- Tests run inside Neovim via busted's `lua = "nvim -u NONE -i NONE -l"` (set in `.busted`)
|
||||||
|
- `test:prepare` installs busted rocks via `luarocks test --prepare` and manages nightly Neovim via `nvimv`
|
||||||
10
lua/u.lua
10
lua/u.lua
@@ -552,12 +552,10 @@ function Range.from_motion(motion, opts)
|
|||||||
local old_eventignore = vim.o.eventignore
|
local old_eventignore = vim.o.eventignore
|
||||||
vim.o.eventignore = 'all'
|
vim.o.eventignore = 'all'
|
||||||
vim.go.operatorfunc = 'v:lua.Range__from_motion_opfunc'
|
vim.go.operatorfunc = 'v:lua.Range__from_motion_opfunc'
|
||||||
vim.cmd {
|
vim.cmd(
|
||||||
cmd = 'normal',
|
'keepjumps normal' .. (not opts.user_defined and '!' or '') .. ' ' .. ESC .. 'g@' .. motion,
|
||||||
bang = not opts.user_defined,
|
{ mods = { silent = true } }
|
||||||
args = { ESC .. 'g@' .. motion },
|
)
|
||||||
mods = { silent = true },
|
|
||||||
}
|
|
||||||
vim.o.eventignore = old_eventignore
|
vim.o.eventignore = old_eventignore
|
||||||
end)
|
end)
|
||||||
local captured_range = _G.Range__from_motion_opfunc_captured_range
|
local captured_range = _G.Range__from_motion_opfunc_captured_range
|
||||||
|
|||||||
16
mise.toml
16
mise.toml
@@ -15,9 +15,11 @@ jq = "1.8.1"
|
|||||||
# but to avoid having to litter a bunch of commands with that environment
|
# but to avoid having to litter a bunch of commands with that environment
|
||||||
# initialization, this just makes things simpler:
|
# initialization, this just makes things simpler:
|
||||||
neovim = "0.12.1"
|
neovim = "0.12.1"
|
||||||
|
"http:nvimv" = { version = "latest", url = "https://raw.githubusercontent.com/jrop/nvimv/refs/heads/main/nvimv" }
|
||||||
stylua = "2.3.1"
|
stylua = "2.3.1"
|
||||||
"cargo:emmylua_ls" = "0.20.0"
|
cargo-binstall = "1.18.1"
|
||||||
"cargo:emmylua_check" = "0.20.0"
|
"cargo:emmylua_ls" = { version = "0.20.0", depends=["cargo-binstall"] }
|
||||||
|
"cargo:emmylua_check" = { version = "0.20.0", depends=["cargo-binstall"] }
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Env
|
# Env
|
||||||
@@ -44,10 +46,10 @@ echo
|
|||||||
if find .prefix -path '**/nightly/**/nvim' -mtime -1 2>/dev/null | grep -q .; then
|
if find .prefix -path '**/nightly/**/nvim' -mtime -1 2>/dev/null | grep -q .; then
|
||||||
echo "Neovim Nightly is up-to-date"
|
echo "Neovim Nightly is up-to-date"
|
||||||
else
|
else
|
||||||
if ./nvimv/nvimv ls | grep nightly >/dev/null; then
|
if nvimv ls | grep nightly >/dev/null; then
|
||||||
./nvimv/nvimv upgrade nightly
|
nvimv upgrade nightly
|
||||||
else
|
else
|
||||||
./nvimv/nvimv install nightly
|
nvimv install nightly
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
@@ -66,8 +68,8 @@ echo Neovim version=$usage_version
|
|||||||
echo -----------------------------
|
echo -----------------------------
|
||||||
echo -----------------------------
|
echo -----------------------------
|
||||||
echo
|
echo
|
||||||
./nvimv/nvimv install $usage_version
|
nvimv install $usage_version
|
||||||
eval $(./nvimv/nvimv env $usage_version)
|
eval $(nvimv env $usage_version)
|
||||||
busted --verbose
|
busted --verbose
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
1
nvimv
1
nvimv
Submodule nvimv deleted from bd5c243b96
@@ -1,6 +1,10 @@
|
|||||||
require 'luacov'
|
require 'luacov'
|
||||||
|
|
||||||
--- @diagnostic disable: undefined-field, need-check-nil
|
--- @diagnostic disable: need-check-nil
|
||||||
|
--- @diagnostic disable: param-type-mismatch
|
||||||
|
--- @diagnostic disable: undefined-field
|
||||||
|
--- @diagnostic disable: unnecessary-assert
|
||||||
|
|
||||||
local Pos = require('u').Pos
|
local Pos = require('u').Pos
|
||||||
local Range = require('u').Range
|
local Range = require('u').Range
|
||||||
local function withbuf(lines, f)
|
local function withbuf(lines, f)
|
||||||
@@ -368,15 +372,91 @@ describe('Range', function()
|
|||||||
vim.api.nvim_buf_delete(buf2, { force = true })
|
vim.api.nvim_buf_delete(buf2, { force = true })
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('from_motion does not modify the jumplist', function()
|
||||||
|
withbuf({
|
||||||
|
'line1',
|
||||||
|
'(hello world)',
|
||||||
|
'line3',
|
||||||
|
'{foo bar}',
|
||||||
|
'line5',
|
||||||
|
}, function()
|
||||||
|
-- Build up a jumplist that includes line 2:
|
||||||
|
vim.cmd.normal { args = { '5G' }, bang = true }
|
||||||
|
vim.cmd.normal { args = { '2G' }, bang = true }
|
||||||
|
vim.cmd.normal { args = { '4G' }, bang = true }
|
||||||
|
vim.cmd.normal { args = { '5G' }, bang = true }
|
||||||
|
|
||||||
|
-- Position cursor on line 2 (which is in the jumplist), inside the parens
|
||||||
|
vim.fn.setpos('.', { 0, 2, 3, 0 })
|
||||||
|
|
||||||
|
local jl_before = vim.fn.getjumplist()
|
||||||
|
local n_before = #jl_before[1]
|
||||||
|
local curjump_before = jl_before[2]
|
||||||
|
|
||||||
|
-- g@a( corrupts the jumplist by deduplicating the entry for line 2.
|
||||||
|
-- This is a query-only operation and should NOT change the jumplist:
|
||||||
|
Range.from_motion('a(', { contains_cursor = true })
|
||||||
|
|
||||||
|
local jl_after = vim.fn.getjumplist()
|
||||||
|
|
||||||
|
assert.are.same(
|
||||||
|
n_before,
|
||||||
|
#jl_after[1],
|
||||||
|
'from_motion should not add or remove jumplist entries'
|
||||||
|
)
|
||||||
|
assert.are.same(
|
||||||
|
curjump_before,
|
||||||
|
jl_after[2],
|
||||||
|
'from_motion should not change the curjump pointer'
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('find_nearest_brackets does not modify the jumplist', function()
|
||||||
|
withbuf({
|
||||||
|
'line1',
|
||||||
|
'(hello world)',
|
||||||
|
'line3',
|
||||||
|
'{foo bar}',
|
||||||
|
'line5',
|
||||||
|
}, function()
|
||||||
|
-- Build up a jumplist:
|
||||||
|
vim.cmd.normal { args = { '5G' }, bang = true }
|
||||||
|
vim.cmd.normal { args = { '2G' }, bang = true }
|
||||||
|
vim.cmd.normal { args = { '4G' }, bang = true }
|
||||||
|
vim.cmd.normal { args = { '5G' }, bang = true }
|
||||||
|
|
||||||
|
-- Move to a line near brackets
|
||||||
|
vim.fn.setpos('.', { 0, 2, 3, 0 })
|
||||||
|
|
||||||
|
local jl_before = vim.fn.getjumplist()
|
||||||
|
local n_before = #jl_before[1]
|
||||||
|
local curjump_before = jl_before[2]
|
||||||
|
|
||||||
|
-- This calls from_motion internally and should NOT change the jumplist:
|
||||||
|
Range.find_nearest_brackets()
|
||||||
|
|
||||||
|
local jl_after = vim.fn.getjumplist()
|
||||||
|
|
||||||
|
assert.are.same(
|
||||||
|
n_before,
|
||||||
|
#jl_after[1],
|
||||||
|
'find_nearest_brackets should not add or remove jumplist entries'
|
||||||
|
)
|
||||||
|
assert.are.same(
|
||||||
|
curjump_before,
|
||||||
|
jl_after[2],
|
||||||
|
'find_nearest_brackets should not change the curjump pointer'
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
it('from_motion restores visual selection when started in visual mode', function()
|
it('from_motion restores visual selection when started in visual mode', function()
|
||||||
withbuf({ 'the quick brown fox' }, function()
|
withbuf({ 'the quick brown fox' }, function()
|
||||||
-- Enter visual mode first
|
-- Enter visual mode first
|
||||||
vim.fn.setpos('.', { 0, 1, 1, 0 })
|
vim.fn.setpos('.', { 0, 1, 1, 0 })
|
||||||
vim.cmd.normal 'vll' -- select 'the' (3 chars)
|
vim.cmd.normal 'vll' -- select 'the' (3 chars)
|
||||||
|
|
||||||
-- Record initial visual marks
|
|
||||||
local initial_v = vim.fn.getpos 'v'
|
|
||||||
|
|
||||||
-- Call from_motion (should save and restore visual selection)
|
-- Call from_motion (should save and restore visual selection)
|
||||||
local range = Range.from_motion 'aw'
|
local range = Range.from_motion 'aw'
|
||||||
assert.is_not_nil(range)
|
assert.is_not_nil(range)
|
||||||
@@ -661,7 +741,7 @@ describe('Range', function()
|
|||||||
-- histget()
|
-- histget()
|
||||||
local orig_histget = vim.fn.histget
|
local orig_histget = vim.fn.histget
|
||||||
--- @diagnostic disable-next-line: duplicate-set-field
|
--- @diagnostic disable-next-line: duplicate-set-field
|
||||||
function vim.fn.histget(x, y) return [['<,'>]] end
|
function vim.fn.histget() return [['<,'>]] end
|
||||||
|
|
||||||
-- Now run the test:
|
-- Now run the test:
|
||||||
local range = Range.from_cmd_args(args) --[[@as u.Range]]
|
local range = Range.from_cmd_args(args) --[[@as u.Range]]
|
||||||
|
|||||||
Reference in New Issue
Block a user