This commit is contained in:
parent
d9bb01be8b
commit
4780b8918b
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -11,5 +11,5 @@ jobs:
|
|||||||
- uses: rhysd/action-setup-vim@v1
|
- uses: rhysd/action-setup-vim@v1
|
||||||
with:
|
with:
|
||||||
neovim: true
|
neovim: true
|
||||||
version: v0.10.1
|
version: v0.11.0
|
||||||
- run: make test
|
- run: make test
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
|
/.lux/
|
||||||
*.src.rock
|
*.src.rock
|
||||||
|
2
.luacheckrc
Normal file
2
.luacheckrc
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- :vim set ft=lua
|
||||||
|
globals = { "vim" }
|
3
Makefile
3
Makefile
@ -3,7 +3,8 @@ PLENARY_DIR=~/.local/share/nvim/site/pack/test/opt/plenary.nvim
|
|||||||
all: lint test
|
all: lint test
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
selene .
|
lua-language-server --check=lua/u/ --checklevel=Hint
|
||||||
|
lux check
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
stylua .
|
stylua .
|
||||||
|
31
README.md
31
README.md
@ -217,7 +217,20 @@ buf:render {
|
|||||||
|
|
||||||
### A note on indices
|
### A note on indices
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<del>
|
||||||
I love NeoVim. I am coming to love Lua. I don't like 1-based indices; perhaps I am too old. Perhaps I am too steeped in the history of loving the elegance of simple pointer arithmetic. Regardless, the way positions are addressed in NeoVim/Vim is (terrifyingly) mixed. Some methods return 1-based, others accept only 0-based. In order to stay sane, I had to make a choice to store everything in one, uniform representation in this library. I chose (what I humbly think is the only sane way) to stick with the tried-and-true 0-based index scheme. That abstraction leaks into the public API of this library.
|
I love NeoVim. I am coming to love Lua. I don't like 1-based indices; perhaps I am too old. Perhaps I am too steeped in the history of loving the elegance of simple pointer arithmetic. Regardless, the way positions are addressed in NeoVim/Vim is (terrifyingly) mixed. Some methods return 1-based, others accept only 0-based. In order to stay sane, I had to make a choice to store everything in one, uniform representation in this library. I chose (what I humbly think is the only sane way) to stick with the tried-and-true 0-based index scheme. That abstraction leaks into the public API of this library.
|
||||||
|
</del>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<b>This has changed in v2</b>. After much thought, I realized that:
|
||||||
|
|
||||||
|
1. The 0-based indexing in NeoVim is prevelant in the `:api`, which is designed to be exposed to many languages. As such, it makes sense for this interface to use 0-based indexing. However, many internal Vim functions use 1-based indexing.
|
||||||
|
2. This is a Lua library (surprise, surprise, duh) - the idioms of the language should take precedence over my preference
|
||||||
|
3. There were subtle bugs in the code where indices weren't being normalized to 0-based, anyways. Somehow it worked most of the time.
|
||||||
|
|
||||||
|
As such, this library now uses 1-based indexing everywhere, doing the necessary interop conversions when calling `:api` functions.
|
||||||
|
|
||||||
### 1. Creating a Range
|
### 1. Creating a Range
|
||||||
|
|
||||||
@ -225,8 +238,8 @@ The `Range` utility is the main feature upon which most other things in this lib
|
|||||||
|
|
||||||
```lua
|
```lua
|
||||||
local Range = require 'u.range'
|
local Range = require 'u.range'
|
||||||
local start = Pos.new(0, 0, 0) -- Line 1, first column
|
local start = Pos.new(0, 1, 1) -- Line 1, first column
|
||||||
local stop = Pos.new(0, 2, 0) -- Line 3, first column
|
local stop = Pos.new(0, 3, 1) -- Line 3, first column
|
||||||
|
|
||||||
Range.new(start, stop, 'v') -- charwise selection
|
Range.new(start, stop, 'v') -- charwise selection
|
||||||
Range.new(start, stop, 'V') -- linewise selection
|
Range.new(start, stop, 'V') -- linewise selection
|
||||||
@ -236,7 +249,7 @@ This is usually not how you want to obtain a `Range`, however. Usually you want
|
|||||||
|
|
||||||
```lua
|
```lua
|
||||||
-- get the first line in a buffer:
|
-- get the first line in a buffer:
|
||||||
Range.from_line(0, 0)
|
Range.from_line(1, 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:
|
||||||
@ -285,8 +298,8 @@ So far, that's a lot of ways to _get_ a `Range`. But what can you do with a rang
|
|||||||
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:line0(0) -- get the first line within this range
|
range:line(1) -- get the first line within this range
|
||||||
range:line0(-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:
|
||||||
range:replace {
|
range:replace {
|
||||||
'replacement line 1',
|
'replacement line 1',
|
||||||
@ -357,10 +370,10 @@ buf:set_var('...', ...)
|
|||||||
buf:all() -- returns a Range representing the entire buffer
|
buf:all() -- returns a Range representing the entire buffer
|
||||||
buf:is_empty() -- returns true if the buffer has no text
|
buf:is_empty() -- returns true if the buffer has no text
|
||||||
buf:append_line '...'
|
buf:append_line '...'
|
||||||
buf:line0(0) -- returns a Range representing the first line in the buffer
|
buf:line(1) -- returns a Range representing the first line in the buffer
|
||||||
buf:line0(-1) -- returns a Range representing the last line in the buffer
|
buf:line(-1) -- returns a Range representing the last line in the buffer
|
||||||
buf:lines(0, 1) -- returns a Range representing the first two lines in the buffer
|
buf:lines(1, 2) -- returns a Range representing the first two lines in the buffer
|
||||||
buf:lines(1, -2) -- returns a Range representing all but the first and last lines of a buffer
|
buf:lines(2, -2) -- returns a Range representing all but the first and last lines of a buffer
|
||||||
buf:text_object('iw') -- returns a Range representing the text object 'iw' in the give buffer
|
buf:text_object('iw') -- returns a Range representing the text object 'iw' in the give buffer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ local Range = require 'u.range'
|
|||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
--- @param bracket_range Range
|
--- @param bracket_range u.Range
|
||||||
--- @param left string
|
--- @param left string
|
||||||
--- @param right string
|
--- @param right string
|
||||||
local function split(bracket_range, left, right)
|
local function split(bracket_range, left, right)
|
||||||
@ -52,7 +52,7 @@ local function split(bracket_range, left, right)
|
|||||||
bracket_range:replace(code.lines)
|
bracket_range:replace(code.lines)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param bracket_range Range
|
--- @param bracket_range u.Range
|
||||||
--- @param left string
|
--- @param left string
|
||||||
--- @param right string
|
--- @param right string
|
||||||
local function join(bracket_range, left, right)
|
local function join(bracket_range, left, right)
|
||||||
|
@ -53,7 +53,7 @@ local function prompt_for_bounds()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param range Range
|
--- @param range u.Range
|
||||||
--- @param bounds { left: string; right: string }
|
--- @param bounds { left: string; right: string }
|
||||||
local function do_surround(range, bounds)
|
local function do_surround(range, bounds)
|
||||||
local left = bounds.left
|
local left = bounds.left
|
||||||
@ -69,7 +69,7 @@ local function do_surround(range, bounds)
|
|||||||
range:replace(left .. range:text() .. right)
|
range:replace(left .. range:text() .. right)
|
||||||
elseif range.mode == 'V' then
|
elseif range.mode == 'V' then
|
||||||
local buf = Buffer.current()
|
local buf = Buffer.current()
|
||||||
local cw = CodeWriter.from_line(buf:line0(range.start.lnum):text(), buf.buf)
|
local cw = CodeWriter.from_line(range.start:line(), buf.buf)
|
||||||
|
|
||||||
-- write the left bound at the current indent level:
|
-- write the left bound at the current indent level:
|
||||||
cw:write(left)
|
cw:write(left)
|
||||||
@ -109,10 +109,8 @@ function _G.MySurroundOpFunc(ty)
|
|||||||
if not vim_repeat.is_repeating() then hl = range:highlight('IncSearch', { priority = 999 }) end
|
if not vim_repeat.is_repeating() then hl = range:highlight('IncSearch', { priority = 999 }) end
|
||||||
|
|
||||||
local bounds = prompt_for_bounds()
|
local bounds = prompt_for_bounds()
|
||||||
if bounds == nil then
|
if hl then hl.clear() end
|
||||||
if hl then hl.clear() end
|
if bounds == nil then return end
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
do_surround(range, bounds)
|
do_surround(range, bounds)
|
||||||
end
|
end
|
||||||
@ -166,10 +164,6 @@ function M.setup()
|
|||||||
hl_clear()
|
hl_clear()
|
||||||
if to == nil then return end
|
if to == nil then return end
|
||||||
|
|
||||||
-- Re-fetch the arange, just in case this action is being repeated:
|
|
||||||
arange = get_fresh_arange()
|
|
||||||
if arange == nil then return end
|
|
||||||
|
|
||||||
if from_c == 't' then
|
if from_c == 't' then
|
||||||
-- For tags, we want to replace the inner text, not the tag:
|
-- For tags, we want to replace the inner text, not the tag:
|
||||||
local irange = Range.from_text_object('i' .. from_c, { user_defined = true })
|
local irange = Range.from_text_object('i' .. from_c, { user_defined = true })
|
||||||
@ -182,7 +176,7 @@ function M.setup()
|
|||||||
lrange:replace(to.left)
|
lrange:replace(to.left)
|
||||||
else
|
else
|
||||||
-- replace `from.right` with `to.right`:
|
-- replace `from.right` with `to.right`:
|
||||||
local last_line = arange:line0(-1).text() --[[@as string]]
|
local last_line = arange:line(-1):text()
|
||||||
local from_right_match = last_line:match(vim.pesc(from.right) .. '$')
|
local from_right_match = last_line:match(vim.pesc(from.right) .. '$')
|
||||||
if from_right_match then
|
if from_right_match then
|
||||||
local match_start = arange.stop:clone()
|
local match_start = arange.stop:clone()
|
||||||
@ -191,7 +185,7 @@ function M.setup()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- replace `from.left` with `to.left`:
|
-- replace `from.left` with `to.left`:
|
||||||
local first_line = arange:line0(0).text() --[[@as string]]
|
local first_line = arange:line(1):text()
|
||||||
local from_left_match = first_line:match('^' .. vim.pesc(from.left))
|
local from_left_match = first_line:match('^' .. vim.pesc(from.left))
|
||||||
if from_left_match then
|
if from_left_match then
|
||||||
local match_end = arange.start:clone()
|
local match_end = arange.start:clone()
|
||||||
@ -240,11 +234,11 @@ function M.setup()
|
|||||||
)
|
)
|
||||||
|
|
||||||
-- delete last line, if it is empty:
|
-- delete last line, if it is empty:
|
||||||
local last = buf:line0(final_range.stop.lnum)
|
local last = buf:line(final_range.stop.lnum)
|
||||||
if last:text():match '^%s*$' then last:replace(nil) end
|
if last:text():match '^%s*$' then last:replace(nil) end
|
||||||
|
|
||||||
-- delete first line, if it is empty:
|
-- delete first line, if it is empty:
|
||||||
local first = buf:line0(final_range.start.lnum)
|
local first = buf:line(final_range.start.lnum)
|
||||||
if first:text():match '^%s*$' then first:replace(nil) end
|
if first:text():match '^%s*$' then first:replace(nil) end
|
||||||
else
|
else
|
||||||
-- trim start:
|
-- trim start:
|
||||||
|
@ -11,8 +11,7 @@ function M.setup()
|
|||||||
|
|
||||||
-- Select current line:
|
-- Select current line:
|
||||||
utils.define_text_object('a.', function()
|
utils.define_text_object('a.', function()
|
||||||
local lnum = Pos.from_pos('.').lnum
|
return Buffer.current():line(Pos.from_pos('.').lnum)
|
||||||
return Buffer.current():line0(lnum)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Select the nearest quote:
|
-- Select the nearest quote:
|
||||||
|
5
lua/u/.luarc.json
Normal file
5
lua/u/.luarc.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||||
|
"diagnostics.globals": ["assert", "vim"],
|
||||||
|
"runtime.version": "LuaJIT"
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
local Range = require 'u.range'
|
local Range = require 'u.range'
|
||||||
local Renderer = require 'u.renderer'.Renderer
|
local Renderer = require 'u.renderer'.Renderer
|
||||||
|
|
||||||
---@class Buffer
|
---@class u.Buffer
|
||||||
---@field buf number
|
---@field buf number
|
||||||
---@field private renderer Renderer
|
---@field private renderer u.Renderer
|
||||||
local Buffer = {}
|
local Buffer = {}
|
||||||
|
|
||||||
---@param buf? number
|
---@param buf? number
|
||||||
---@return Buffer
|
---@return u.Buffer
|
||||||
function Buffer.from_nr(buf)
|
function Buffer.from_nr(buf)
|
||||||
if buf == nil or buf == 0 then buf = vim.api.nvim_get_current_buf() end
|
if buf == nil or buf == 0 then buf = vim.api.nvim_get_current_buf() end
|
||||||
|
|
||||||
@ -18,12 +18,12 @@ function Buffer.from_nr(buf)
|
|||||||
}, { __index = Buffer })
|
}, { __index = Buffer })
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return Buffer
|
---@return u.Buffer
|
||||||
function Buffer.current() return Buffer.from_nr(0) end
|
function Buffer.current() return Buffer.from_nr(0) end
|
||||||
|
|
||||||
---@param listed boolean
|
---@param listed boolean
|
||||||
---@param scratch boolean
|
---@param scratch boolean
|
||||||
---@return Buffer
|
---@return u.Buffer
|
||||||
function Buffer.create(listed, scratch) return Buffer.from_nr(vim.api.nvim_create_buf(listed, scratch)) end
|
function Buffer.create(listed, scratch) return Buffer.from_nr(vim.api.nvim_create_buf(listed, scratch)) end
|
||||||
|
|
||||||
function Buffer:set_tmp_options()
|
function Buffer:set_tmp_options()
|
||||||
@ -48,7 +48,7 @@ function Buffer:line_count() return vim.api.nvim_buf_line_count(self.buf) end
|
|||||||
|
|
||||||
function Buffer:all() return Range.from_buf_text(self.buf) end
|
function Buffer:all() return Range.from_buf_text(self.buf) end
|
||||||
|
|
||||||
function Buffer:is_empty() return self:line_count() == 1 and self:line0(0):text() == '' end
|
function Buffer:is_empty() return self:line_count() == 1 and self:line(1):text() == '' end
|
||||||
|
|
||||||
---@param line string
|
---@param line string
|
||||||
function Buffer:append_line(line)
|
function Buffer:append_line(line)
|
||||||
@ -58,8 +58,8 @@ function Buffer:append_line(line)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param num number 0-based line index
|
---@param num number 0-based line index
|
||||||
function Buffer:line0(num)
|
function Buffer:line(num)
|
||||||
if num < 0 then return self:line0(self:line_count() + num) end
|
if num < 0 then num = self:line_count() + num + 1 end
|
||||||
return Range.from_line(self.buf, num)
|
return Range.from_line(self.buf, num)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -68,13 +68,14 @@ end
|
|||||||
function Buffer:lines(start, stop) return Range.from_lines(self.buf, start, stop) end
|
function Buffer:lines(start, stop) return Range.from_lines(self.buf, start, stop) end
|
||||||
|
|
||||||
---@param txt_obj string
|
---@param txt_obj string
|
||||||
---@param opts? { contains_cursor?: boolean; pos?: Pos }
|
---@param opts? { contains_cursor?: boolean; pos?: u.Pos }
|
||||||
function Buffer:text_object(txt_obj, opts)
|
function Buffer:text_object(txt_obj, opts)
|
||||||
opts = vim.tbl_extend('force', opts or {}, { buf = self.buf })
|
opts = vim.tbl_extend('force', opts or {}, { buf = self.buf })
|
||||||
return Range.from_text_object(txt_obj, opts)
|
return Range.from_text_object(txt_obj, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param event string|string[]
|
--- @param event string|string[]
|
||||||
|
--- @diagnostic disable-next-line: undefined-doc-name
|
||||||
--- @param opts vim.api.keyset.create_autocmd
|
--- @param opts vim.api.keyset.create_autocmd
|
||||||
function Buffer:autocmd(event, opts)
|
function Buffer:autocmd(event, opts)
|
||||||
vim.api.nvim_create_autocmd(event, vim.tbl_extend('force', opts, { buffer = self.buf }))
|
vim.api.nvim_create_autocmd(event, vim.tbl_extend('force', opts, { buffer = self.buf }))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
local Buffer = require 'u.buffer'
|
local Buffer = require 'u.buffer'
|
||||||
|
|
||||||
---@class CodeWriter
|
---@class u.CodeWriter
|
||||||
---@field lines string[]
|
---@field lines string[]
|
||||||
---@field indent_level number
|
---@field indent_level number
|
||||||
---@field indent_str string
|
---@field indent_str string
|
||||||
@ -8,7 +8,7 @@ local CodeWriter = {}
|
|||||||
|
|
||||||
---@param indent_level? number
|
---@param indent_level? number
|
||||||
---@param indent_str? string
|
---@param indent_str? string
|
||||||
---@return CodeWriter
|
---@return u.CodeWriter
|
||||||
function CodeWriter.new(indent_level, indent_str)
|
function CodeWriter.new(indent_level, indent_str)
|
||||||
if indent_level == nil then indent_level = 0 end
|
if indent_level == nil then indent_level = 0 end
|
||||||
if indent_str == nil then indent_str = ' ' end
|
if indent_str == nil then indent_str = ' ' end
|
||||||
@ -22,9 +22,9 @@ function CodeWriter.new(indent_level, indent_str)
|
|||||||
return cw
|
return cw
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param p Pos
|
---@param p u.Pos
|
||||||
function CodeWriter.from_pos(p)
|
function CodeWriter.from_pos(p)
|
||||||
local line = Buffer.from_nr(p.buf):line0(p.lnum):text()
|
local line = Buffer.from_nr(p.buf):line(p.lnum):text()
|
||||||
return CodeWriter.from_line(line, p.buf)
|
return CodeWriter.from_line(line, p.buf)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ end
|
|||||||
---@param line string
|
---@param line string
|
||||||
function CodeWriter:write(line) self:write_raw(self.indent_str:rep(self.indent_level) .. line) end
|
function CodeWriter:write(line) self:write_raw(self.indent_str:rep(self.indent_level) .. line) end
|
||||||
|
|
||||||
---@param f? fun(cw: CodeWriter):any
|
---@param f? fun(cw: u.CodeWriter):any
|
||||||
function CodeWriter:indent(f)
|
function CodeWriter:indent(f)
|
||||||
local cw = {
|
local cw = {
|
||||||
lines = self.lines,
|
lines = self.lines,
|
||||||
|
@ -7,7 +7,7 @@ function M.file_for_name(name) return vim.fs.joinpath(vim.fn.stdpath 'cache', 'u
|
|||||||
-- Logger class
|
-- Logger class
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
--- @class Logger
|
--- @class u.Logger
|
||||||
--- @field name string
|
--- @field name string
|
||||||
--- @field private fd number
|
--- @field private fd number
|
||||||
local Logger = {}
|
local Logger = {}
|
||||||
|
@ -1,24 +1,17 @@
|
|||||||
local Range = require 'u.range'
|
local Range = require 'u.range'
|
||||||
local vim_repeat = require 'u.repeat'
|
|
||||||
|
|
||||||
---@type fun(range: Range): nil|(fun():any)
|
--- @type fun(range: u.Range): nil|(fun():any)
|
||||||
local __U__OpKeymapOpFunc_rhs = nil
|
local __U__OpKeymapOpFunc_rhs = nil
|
||||||
|
|
||||||
--- This is the global utility function used for operatorfunc
|
--- This is the global utility function used for operatorfunc
|
||||||
--- in opkeymap
|
--- in opkeymap
|
||||||
---@type nil|fun(range: Range): fun():any|nil
|
--- @type nil|fun(range: u.Range): fun():any|nil
|
||||||
---@param ty 'line'|'char'|'block'
|
--- @param ty 'line'|'char'|'block'
|
||||||
-- selene: allow(unused_variable)
|
-- selene: allow(unused_variable)
|
||||||
function __U__OpKeymapOpFunc(ty)
|
function __U__OpKeymapOpFunc(ty)
|
||||||
if __U__OpKeymapOpFunc_rhs ~= nil then
|
if __U__OpKeymapOpFunc_rhs ~= nil then
|
||||||
local range = Range.from_op_func(ty)
|
local range = Range.from_op_func(ty)
|
||||||
local repeat_inject = __U__OpKeymapOpFunc_rhs(range)
|
__U__OpKeymapOpFunc_rhs(range)
|
||||||
|
|
||||||
vim_repeat.set(function()
|
|
||||||
vim.o.operatorfunc = 'v:lua.__U__OpKeymapOpFunc'
|
|
||||||
if repeat_inject ~= nil and type(repeat_inject) == 'function' then repeat_inject() end
|
|
||||||
vim_repeat.native_repeat()
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -28,12 +21,17 @@ end
|
|||||||
--- g@: tells vim to way for a motion, and then call operatorfunc.
|
--- g@: tells vim to way for a motion, and then call operatorfunc.
|
||||||
--- 2. The operatorfunc is set to a lua function that computes the range being operated over, that
|
--- 2. The operatorfunc is set to a lua function that computes the range being operated over, that
|
||||||
--- then calls the original passed callback with said range.
|
--- then calls the original passed callback with said range.
|
||||||
---@param mode string|string[]
|
--- @param mode string|string[]
|
||||||
---@param lhs string
|
--- @param lhs string
|
||||||
---@param rhs fun(range: Range): nil|(fun():any) This function may return another function, which is called whenever the operator is repeated
|
--- @param rhs fun(range: u.Range): nil
|
||||||
---@param opts? vim.keymap.set.Opts
|
--- @diagnostic disable-next-line: undefined-doc-name
|
||||||
|
--- @param opts? vim.keymap.set.Opts
|
||||||
local function opkeymap(mode, lhs, rhs, opts)
|
local function opkeymap(mode, lhs, rhs, opts)
|
||||||
vim.keymap.set(mode, lhs, function()
|
vim.keymap.set(mode, lhs, function()
|
||||||
|
-- We don't need to wrap the operation in a repeat, because expr mappings are
|
||||||
|
-- repeated seamlessly by Vim anyway. In addition, the u.repeat:`.` mapping will
|
||||||
|
-- set IS_REPEATING to true, so that callbacks can check if they should used cached
|
||||||
|
-- values.
|
||||||
__U__OpKeymapOpFunc_rhs = rhs
|
__U__OpKeymapOpFunc_rhs = rhs
|
||||||
vim.o.operatorfunc = 'v:lua.__U__OpKeymapOpFunc'
|
vim.o.operatorfunc = 'v:lua.__U__OpKeymapOpFunc'
|
||||||
return 'g@'
|
return 'g@'
|
||||||
|
104
lua/u/pos.lua
104
lua/u/pos.lua
@ -1,10 +1,10 @@
|
|||||||
local MAX_COL = vim.v.maxcol
|
local MAX_COL = vim.v.maxcol
|
||||||
|
|
||||||
---@param buf number
|
---@param buf number
|
||||||
---@param lnum number
|
---@param lnum number 1-based
|
||||||
local function line_text(buf, lnum) return vim.api.nvim_buf_get_lines(buf, lnum, lnum + 1, false)[1] end
|
local function line_text(buf, lnum) return vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] end
|
||||||
|
|
||||||
---@class Pos
|
---@class u.Pos
|
||||||
---@field buf number buffer number
|
---@field buf number buffer number
|
||||||
---@field lnum number 1-based line index
|
---@field lnum number 1-based line index
|
||||||
---@field col number 1-based column index
|
---@field col number 1-based column index
|
||||||
@ -13,10 +13,10 @@ local Pos = {}
|
|||||||
Pos.MAX_COL = MAX_COL
|
Pos.MAX_COL = MAX_COL
|
||||||
|
|
||||||
---@param buf? number
|
---@param buf? number
|
||||||
---@param lnum number
|
---@param lnum number 1-based
|
||||||
---@param col number
|
---@param col number 1-based
|
||||||
---@param off? number
|
---@param off? number
|
||||||
---@return Pos
|
---@return u.Pos
|
||||||
function Pos.new(buf, lnum, col, off)
|
function Pos.new(buf, lnum, col, off)
|
||||||
if buf == nil or buf == 0 then buf = vim.api.nvim_get_current_buf() end
|
if buf == nil or buf == 0 then buf = vim.api.nvim_get_current_buf() end
|
||||||
if off == nil then off = 0 end
|
if off == nil then off = 0 end
|
||||||
@ -44,103 +44,114 @@ function Pos.new(buf, lnum, col, off)
|
|||||||
return pos
|
return pos
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Pos.invalid() return Pos.new(0, 0, 0, 0) end
|
||||||
|
|
||||||
function Pos.is(x)
|
function Pos.is(x)
|
||||||
|
if not type(x) == 'table' then return false end
|
||||||
local mt = getmetatable(x)
|
local mt = getmetatable(x)
|
||||||
return mt and mt.__index == Pos
|
return mt and mt.__index == Pos
|
||||||
end
|
end
|
||||||
|
|
||||||
function Pos.__lt(a, b) return a.lnum < b.lnum or (a.lnum == b.lnum and a.col < b.col) end
|
function Pos.__lt(a, b) return a.lnum < b.lnum or (a.lnum == b.lnum and a.col < b.col) end
|
||||||
function Pos.__le(a, b) return a < b or a == b end
|
function Pos.__le(a, b) return a < b or a == b end
|
||||||
function Pos.__eq(a, b) return a.lnum == b.lnum and a.col == b.col end
|
function Pos.__eq(a, b) return Pos.is(a) and Pos.is(b) and a.buf == b.buf and a.lnum == b.lnum and a.col == b.col end
|
||||||
|
function Pos.__add(x, y)
|
||||||
|
if type(x) == 'number' then
|
||||||
|
x, y = y, x
|
||||||
|
end
|
||||||
|
if not Pos.is(x) or type(y) ~= 'number' then return nil end
|
||||||
|
return x:next(y)
|
||||||
|
end
|
||||||
|
function Pos.__sub(x, y)
|
||||||
|
if type(x) == 'number' then
|
||||||
|
x, y = y, x
|
||||||
|
end
|
||||||
|
if not Pos.is(x) or type(y) ~= 'number' then return nil end
|
||||||
|
return x:next(-y)
|
||||||
|
end
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@return Pos
|
---@return u.Pos
|
||||||
function Pos.from_pos(name)
|
function Pos.from_pos(name)
|
||||||
local p = vim.fn.getpos(name)
|
local p = vim.fn.getpos(name)
|
||||||
local col = p[3]
|
return Pos.new(p[1], p[2], p[3], p[4])
|
||||||
if col ~= MAX_COL then col = col - 1 end
|
|
||||||
return Pos.new(p[1], p[2] - 1, col, p[4])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Pos:is_invalid() return self.buf == 0 and self.lnum == 0 and self.col == 0 and self.off == 0 end
|
||||||
|
|
||||||
function Pos:clone() return Pos.new(self.buf, self.lnum, self.col, self.off) end
|
function Pos:clone() return Pos.new(self.buf, self.lnum, self.col, self.off) end
|
||||||
|
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function Pos:is_col_max() return self.col == MAX_COL end
|
function Pos:is_col_max() return self.col == MAX_COL end
|
||||||
|
|
||||||
---@return number[]
|
|
||||||
function Pos:as_vim() return { self.buf, self.lnum, self.col, self.off } end
|
|
||||||
|
|
||||||
--- Normalize the position to a real position (take into account vim.v.maxcol).
|
--- Normalize the position to a real position (take into account vim.v.maxcol).
|
||||||
function Pos:as_real()
|
function Pos:as_real()
|
||||||
|
local maxlen = #line_text(self.buf, self.lnum)
|
||||||
local col = self.col
|
local col = self.col
|
||||||
if self:is_col_max() then
|
if col > maxlen then
|
||||||
-- We could use utilities in this file to get the given line, but
|
-- We could use utilities in this file to get the given line, but
|
||||||
-- since this is a low-level function, we are going to optimize and
|
-- since this is a low-level function, we are going to optimize and
|
||||||
-- use the API directly:
|
-- use the API directly:
|
||||||
col = #line_text(self.buf, self.lnum) - 1
|
col = maxlen
|
||||||
end
|
end
|
||||||
return Pos.new(self.buf, self.lnum, col, self.off)
|
return Pos.new(self.buf, self.lnum, col, self.off)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param pos string
|
function Pos:as_vim() return { self.buf, self.lnum, self.col, self.off } end
|
||||||
function Pos:save_to_pos(pos)
|
|
||||||
if pos == '.' then
|
|
||||||
vim.api.nvim_win_set_cursor(0, { self.lnum + 1, self.col })
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local p = self:as_real()
|
---@param pos string
|
||||||
vim.fn.setpos(pos, { p.buf, p.lnum + 1, p.col + 1, p.off })
|
function Pos:save_to_pos(pos) vim.fn.setpos(pos, { self.buf, self.lnum, self.col, self.off }) end
|
||||||
end
|
|
||||||
|
|
||||||
---@param mark string
|
---@param mark string
|
||||||
function Pos:save_to_mark(mark)
|
function Pos:save_to_mark(mark)
|
||||||
local p = self:as_real()
|
local p = self:as_real()
|
||||||
vim.api.nvim_buf_set_mark(p.buf, mark, p.lnum + 1, p.col, {})
|
vim.api.nvim_buf_set_mark(p.buf, mark, p.lnum, p.col, {})
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string
|
---@return string
|
||||||
function Pos:char()
|
function Pos:char()
|
||||||
local line = line_text(self.buf, self.lnum)
|
local line = line_text(self.buf, self.lnum)
|
||||||
if line == nil then return '' end
|
if line == nil then return '' end
|
||||||
return line:sub(self.col + 1, self.col + 1)
|
return line:sub(self.col, self.col)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Pos:line() return line_text(self.buf, self.lnum) end
|
||||||
|
|
||||||
---@param dir? -1|1
|
---@param dir? -1|1
|
||||||
---@param must? boolean
|
---@param must? boolean
|
||||||
---@return Pos|nil
|
---@return u.Pos|nil
|
||||||
function Pos:next(dir, must)
|
function Pos:next(dir, must)
|
||||||
if must == nil then must = false end
|
if must == nil then must = false end
|
||||||
|
|
||||||
if dir == nil or dir == 1 then
|
if dir == nil or dir == 1 then
|
||||||
-- Next:
|
-- Next:
|
||||||
local num_lines = vim.api.nvim_buf_line_count(self.buf)
|
local num_lines = vim.api.nvim_buf_line_count(self.buf)
|
||||||
local last_line = line_text(self.buf, num_lines - 1) -- buf:line0(-1)
|
local last_line = line_text(self.buf, num_lines)
|
||||||
if self.lnum == num_lines - 1 and self.col == (#last_line - 1) then
|
if self.lnum == num_lines and self.col == #last_line then
|
||||||
if must then error 'error in Pos:next(): Pos:next() returned nil' end
|
if must then error 'error in Pos:next(): Pos:next() returned nil' end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local col = self.col + 1
|
local col = self.col + 1
|
||||||
local line = self.lnum
|
local line = self.lnum
|
||||||
local line_max_col = #line_text(self.buf, self.lnum) - 1
|
local line_max_col = #line_text(self.buf, self.lnum)
|
||||||
if col > line_max_col then
|
if col > line_max_col then
|
||||||
col = 0
|
col = 1
|
||||||
line = line + 1
|
line = line + 1
|
||||||
end
|
end
|
||||||
return Pos.new(self.buf, line, col, self.off)
|
return Pos.new(self.buf, line, col, self.off)
|
||||||
else
|
else
|
||||||
-- Previous:
|
-- Previous:
|
||||||
if self.col == 0 and self.lnum == 0 then
|
if self.col == 1 and self.lnum == 1 then
|
||||||
if must then error 'error in Pos:next(): Pos:next() returned nil' end
|
if must then error 'error in Pos:next(): Pos:next() returned nil' end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local col = self.col - 1
|
local col = self.col - 1
|
||||||
local line = self.lnum
|
local line = self.lnum
|
||||||
local prev_line_max_col = #(line_text(self.buf, self.lnum - 1) or '') - 1
|
local prev_line_max_col = #(line_text(self.buf, self.lnum - 1) or '')
|
||||||
if col < 0 then
|
if col < 1 then
|
||||||
col = math.max(prev_line_max_col, 0)
|
col = math.max(prev_line_max_col, 1)
|
||||||
line = line - 1
|
line = line - 1
|
||||||
end
|
end
|
||||||
return Pos.new(self.buf, line, col, self.off)
|
return Pos.new(self.buf, line, col, self.off)
|
||||||
@ -155,7 +166,7 @@ function Pos:must_next(dir)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param dir -1|1
|
---@param dir -1|1
|
||||||
---@param predicate fun(p: Pos): boolean
|
---@param predicate fun(p: u.Pos): boolean
|
||||||
---@param test_current? boolean
|
---@param test_current? boolean
|
||||||
function Pos:next_while(dir, predicate, test_current)
|
function Pos:next_while(dir, predicate, test_current)
|
||||||
if test_current and not predicate(self) then return end
|
if test_current and not predicate(self) then return end
|
||||||
@ -169,14 +180,14 @@ function Pos:next_while(dir, predicate, test_current)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param dir -1|1
|
---@param dir -1|1
|
||||||
---@param predicate string|fun(p: Pos): boolean
|
---@param predicate string|fun(p: u.Pos): boolean
|
||||||
function Pos:find_next(dir, predicate)
|
function Pos:find_next(dir, predicate)
|
||||||
if type(predicate) == 'string' then
|
if type(predicate) == 'string' then
|
||||||
local s = predicate
|
local s = predicate
|
||||||
predicate = function(p) return s == p:char() end
|
predicate = function(p) return s == p:char() end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type Pos|nil
|
---@type u.Pos|nil
|
||||||
local curr = self
|
local curr = self
|
||||||
while curr ~= nil do
|
while curr ~= nil do
|
||||||
if predicate(curr) then return curr end
|
if predicate(curr) then return curr end
|
||||||
@ -187,8 +198,8 @@ end
|
|||||||
|
|
||||||
--- Finds the matching bracket/paren for the current position.
|
--- Finds the matching bracket/paren for the current position.
|
||||||
---@param max_chars? number|nil
|
---@param max_chars? number|nil
|
||||||
---@param invocations? Pos[]
|
---@param invocations? u.Pos[]
|
||||||
---@return Pos|nil
|
---@return u.Pos|nil
|
||||||
function Pos:find_match(max_chars, invocations)
|
function Pos:find_match(max_chars, invocations)
|
||||||
if invocations == nil then invocations = {} end
|
if invocations == nil then invocations = {} end
|
||||||
if vim.tbl_contains(invocations, function(p) return self == p end, { predicate = true }) then return nil end
|
if vim.tbl_contains(invocations, function(p) return self == p end, { predicate = true }) then return nil end
|
||||||
@ -202,11 +213,16 @@ function Pos:find_match(max_chars, invocations)
|
|||||||
if not is_opener and not is_closer then return nil end
|
if not is_opener and not is_closer then return nil end
|
||||||
|
|
||||||
local i, _ = vim.iter(is_opener and openers or closers):enumerate():find(function(_, c2) return c == c2 end)
|
local i, _ = vim.iter(is_opener and openers or closers):enumerate():find(function(_, c2) return c == c2 end)
|
||||||
|
-- Store the character we will be looking for:
|
||||||
local c_match = (is_opener and closers or openers)[i]
|
local c_match = (is_opener and closers or openers)[i]
|
||||||
|
|
||||||
---@type Pos|nil
|
---@type u.Pos|nil
|
||||||
local cur = self
|
local cur = self
|
||||||
---@return Pos|nil
|
--- `adv` is a helper that moves the current position backward or forward,
|
||||||
|
--- depending on whether we are looking for an opener or a closer. It returns
|
||||||
|
--- nil if 1) the watch-dog `max_chars` falls bellow 0, or 2) if we have gone
|
||||||
|
--- beyond the beginning/end of the file.
|
||||||
|
---@return u.Pos|nil
|
||||||
local function adv()
|
local function adv()
|
||||||
if cur == nil then return nil end
|
if cur == nil then return nil end
|
||||||
|
|
||||||
@ -218,7 +234,7 @@ function Pos:find_match(max_chars, invocations)
|
|||||||
return cur:next(is_opener and 1 or -1)
|
return cur:next(is_opener and 1 or -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- scan until we find a match:
|
-- scan until we find `c_match`:
|
||||||
cur = adv()
|
cur = adv()
|
||||||
while cur ~= nil and cur:char() ~= c_match do
|
while cur ~= nil and cur:char() ~= c_match do
|
||||||
cur = adv()
|
cur = adv()
|
||||||
|
343
lua/u/range.lua
343
lua/u/range.lua
@ -1,5 +1,4 @@
|
|||||||
local Pos = require 'u.pos'
|
local Pos = require 'u.pos'
|
||||||
local State = require 'u.state'
|
|
||||||
|
|
||||||
local orig_on_yank = (vim.hl or vim.highlight).on_yank
|
local orig_on_yank = (vim.hl or vim.highlight).on_yank
|
||||||
local on_yank_enabled = true;
|
local on_yank_enabled = true;
|
||||||
@ -8,16 +7,16 @@ local on_yank_enabled = true;
|
|||||||
return orig_on_yank(opts)
|
return orig_on_yank(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class Range
|
---@class u.Range
|
||||||
---@field start Pos
|
---@field start u.Pos
|
||||||
---@field stop Pos|nil
|
---@field stop u.Pos|nil
|
||||||
---@field mode 'v'|'V'
|
---@field mode 'v'|'V'
|
||||||
local Range = {}
|
local Range = {}
|
||||||
|
|
||||||
---@param start Pos
|
---@param start u.Pos
|
||||||
---@param stop Pos|nil
|
---@param stop u.Pos|nil
|
||||||
---@param mode? 'v'|'V'
|
---@param mode? 'v'|'V'
|
||||||
---@return Range
|
---@return u.Range
|
||||||
function Range.new(start, stop, mode)
|
function Range.new(start, stop, mode)
|
||||||
if stop ~= nil and stop < start then
|
if stop ~= nil and stop < start then
|
||||||
start, stop = stop, start
|
start, stop = stop, start
|
||||||
@ -25,7 +24,7 @@ function Range.new(start, stop, mode)
|
|||||||
|
|
||||||
local r = { start = start, stop = stop, mode = mode or 'v' }
|
local r = { start = start, stop = stop, mode = mode or 'v' }
|
||||||
local function str()
|
local function str()
|
||||||
---@param p Pos
|
---@param p u.Pos
|
||||||
local function posstr(p)
|
local function posstr(p)
|
||||||
if p == nil then
|
if p == nil then
|
||||||
return 'nil'
|
return 'nil'
|
||||||
@ -51,7 +50,7 @@ end
|
|||||||
|
|
||||||
---@param lpos string
|
---@param lpos string
|
||||||
---@param rpos string
|
---@param rpos string
|
||||||
---@return Range
|
---@return u.Range
|
||||||
function Range.from_marks(lpos, rpos)
|
function Range.from_marks(lpos, rpos)
|
||||||
local start = Pos.from_pos(lpos)
|
local start = Pos.from_pos(lpos)
|
||||||
local stop = Pos.from_pos(rpos)
|
local stop = Pos.from_pos(rpos)
|
||||||
@ -72,15 +71,17 @@ function Range.from_buf_text(buf)
|
|||||||
if buf == nil or buf == 0 then buf = vim.api.nvim_get_current_buf() end
|
if buf == nil or buf == 0 then buf = vim.api.nvim_get_current_buf() end
|
||||||
local num_lines = vim.api.nvim_buf_line_count(buf)
|
local num_lines = vim.api.nvim_buf_line_count(buf)
|
||||||
|
|
||||||
local start = Pos.new(buf, 0, 0)
|
local start = Pos.new(buf, 1, 1)
|
||||||
local stop = Pos.new(buf, num_lines - 1, Pos.MAX_COL)
|
local stop = Pos.new(buf, num_lines, Pos.MAX_COL)
|
||||||
return Range.new(start, stop, 'V')
|
return Range.new(start, stop, 'V')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- TODO: make 1-based
|
||||||
---@param buf? number
|
---@param buf? number
|
||||||
---@param line number 0-based line index
|
---@param line number 0-based line index
|
||||||
function Range.from_line(buf, line) return Range.from_lines(buf, line, line) end
|
function Range.from_line(buf, line) return Range.from_lines(buf, line, line) end
|
||||||
|
|
||||||
|
-- TODO: make 1-based
|
||||||
---@param buf? number
|
---@param buf? number
|
||||||
---@param start_line number 0-based line index
|
---@param start_line number 0-based line index
|
||||||
---@param stop_line number 0-based line index
|
---@param stop_line number 0-based line index
|
||||||
@ -90,12 +91,12 @@ function Range.from_lines(buf, start_line, stop_line)
|
|||||||
local num_lines = vim.api.nvim_buf_line_count(buf)
|
local num_lines = vim.api.nvim_buf_line_count(buf)
|
||||||
stop_line = num_lines + stop_line
|
stop_line = num_lines + stop_line
|
||||||
end
|
end
|
||||||
return Range.new(Pos.new(buf, start_line, 0), Pos.new(buf, stop_line, Pos.MAX_COL), 'V')
|
return Range.new(Pos.new(buf, start_line + 1, 1), Pos.new(buf, stop_line + 1, Pos.MAX_COL), 'V')
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param text_obj string
|
---@param text_obj string
|
||||||
---@param opts? { buf?: number; contains_cursor?: boolean; pos?: Pos, user_defined?: boolean }
|
---@param opts? { buf?: number; contains_cursor?: boolean; pos?: u.Pos, user_defined?: boolean }
|
||||||
---@return Range|nil
|
---@return u.Range|nil
|
||||||
function Range.from_text_object(text_obj, opts)
|
function Range.from_text_object(text_obj, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
if opts.buf == nil then opts.buf = vim.api.nvim_get_current_buf() end
|
if opts.buf == nil then opts.buf = vim.api.nvim_get_current_buf() end
|
||||||
@ -108,52 +109,58 @@ function Range.from_text_object(text_obj, opts)
|
|||||||
local is_quote = vim.tbl_contains({ "'", '"', '`' }, obj_type)
|
local is_quote = vim.tbl_contains({ "'", '"', '`' }, obj_type)
|
||||||
local cursor = Pos.from_pos '.'
|
local cursor = Pos.from_pos '.'
|
||||||
|
|
||||||
-- Yank, then read '[ and '] to know the bounds:
|
--- @type u.Pos
|
||||||
---@type { start: Pos; stop: Pos }
|
local start
|
||||||
local positions
|
--- @type u.Pos
|
||||||
|
local stop
|
||||||
|
|
||||||
vim.api.nvim_buf_call(opts.buf, function()
|
vim.api.nvim_buf_call(opts.buf, function()
|
||||||
positions = State.run(0, function(s)
|
local original_state = {
|
||||||
s:track_winview()
|
winview = vim.fn.winsaveview(),
|
||||||
s:track_register '"'
|
regquote = vim.fn.getreg '"',
|
||||||
s:track_pos '.'
|
posdot = vim.fn.getpos '.',
|
||||||
s:track_pos "'["
|
poslb = vim.fn.getpos "'[",
|
||||||
s:track_pos "']"
|
posrb = vim.fn.getpos "']",
|
||||||
|
}
|
||||||
|
|
||||||
if opts.pos ~= nil then opts.pos:save_to_pos '.' end
|
if opts.pos ~= nil then opts.pos:save_to_pos '.' end
|
||||||
|
|
||||||
local null_pos = Pos.new(0, 0, 0, 0)
|
Pos.invalid():save_to_pos "'["
|
||||||
null_pos:save_to_pos "'["
|
Pos.invalid():save_to_pos "']"
|
||||||
null_pos:save_to_pos "']"
|
|
||||||
|
|
||||||
local prev_on_yank_enabled = on_yank_enabled
|
local prev_on_yank_enabled = on_yank_enabled
|
||||||
on_yank_enabled = false
|
on_yank_enabled = false
|
||||||
vim.cmd {
|
vim.cmd {
|
||||||
cmd = 'normal',
|
cmd = 'normal',
|
||||||
bang = not opts.user_defined,
|
bang = not opts.user_defined,
|
||||||
args = { '""y' .. text_obj },
|
args = { '""y' .. text_obj },
|
||||||
mods = { silent = true },
|
mods = { silent = true },
|
||||||
}
|
}
|
||||||
on_yank_enabled = prev_on_yank_enabled
|
on_yank_enabled = prev_on_yank_enabled
|
||||||
|
|
||||||
local start = Pos.from_pos "'["
|
start = Pos.from_pos "'["
|
||||||
local stop = Pos.from_pos "']"
|
stop = Pos.from_pos "']"
|
||||||
|
|
||||||
if
|
-- Restore original state:
|
||||||
-- I have no idea why, but when yanking `i"`, the stop-mark is
|
vim.fn.winrestview(original_state.winview)
|
||||||
-- placed on the ending quote. For other text-objects, the stop-
|
vim.fn.setreg('"', original_state.regquote)
|
||||||
-- mark is placed before the closing character.
|
vim.fn.setpos('.', original_state.posdot)
|
||||||
(is_quote and selection_type == 'i' and stop:char() == obj_type)
|
vim.fn.setpos("'[", original_state.poslb)
|
||||||
-- *Sigh*, this also sometimes happens for `it` as well.
|
vim.fn.setpos("']", original_state.posrb)
|
||||||
or (text_obj == 'it' and stop:char() == '<')
|
|
||||||
then
|
if
|
||||||
stop = stop:next(-1) or stop
|
-- I have no idea why, but when yanking `i"`, the stop-mark is
|
||||||
end
|
-- placed on the ending quote. For other text-objects, the stop-
|
||||||
return { start = start, stop = stop }
|
-- mark is placed before the closing character.
|
||||||
end)
|
(is_quote and selection_type == 'i' and stop:char() == obj_type)
|
||||||
|
-- *Sigh*, this also sometimes happens for `it` as well.
|
||||||
|
or (text_obj == 'it' and stop:char() == '<')
|
||||||
|
then
|
||||||
|
stop = stop:next(-1) or stop
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
local start = positions.start
|
|
||||||
local stop = positions.stop
|
if start == stop and start:is_invalid() then return nil end
|
||||||
if start == stop and start.lnum == 0 and start.col == 0 and start.off == 0 then return nil end
|
|
||||||
if opts.contains_cursor and not Range.new(start, stop):contains(cursor) then return nil end
|
if opts.contains_cursor and not Range.new(start, stop):contains(cursor) then return nil end
|
||||||
|
|
||||||
if is_quote and selection_type == 'a' then
|
if is_quote and selection_type == 'a' then
|
||||||
@ -189,11 +196,11 @@ end
|
|||||||
|
|
||||||
--- Get range information from command arguments.
|
--- Get range information from command arguments.
|
||||||
---@param args unknown
|
---@param args unknown
|
||||||
---@return Range|nil
|
---@return u.Range|nil
|
||||||
function Range.from_cmd_args(args)
|
function Range.from_cmd_args(args)
|
||||||
---@type 'v'|'V'
|
---@type 'v'|'V'
|
||||||
local mode
|
local mode
|
||||||
---@type nil|Pos
|
---@type nil|u.Pos
|
||||||
local start
|
local start
|
||||||
local stop
|
local stop
|
||||||
if args.range == 0 then
|
if args.range == 0 then
|
||||||
@ -201,59 +208,42 @@ function Range.from_cmd_args(args)
|
|||||||
else
|
else
|
||||||
start = Pos.from_pos "'<"
|
start = Pos.from_pos "'<"
|
||||||
stop = Pos.from_pos "'>"
|
stop = Pos.from_pos "'>"
|
||||||
if stop:is_col_max() then
|
mode = stop:is_col_max() and 'V' or 'v'
|
||||||
mode = 'V'
|
|
||||||
else
|
|
||||||
mode = 'v'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return Range.new(start, stop, mode)
|
return Range.new(start, stop, mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
---
|
---
|
||||||
function Range.find_nearest_brackets()
|
function Range.find_nearest_brackets()
|
||||||
local a = Range.from_text_object('a<', { contains_cursor = true })
|
return Range.smallest {
|
||||||
local b = Range.from_text_object('a[', { contains_cursor = true })
|
Range.from_text_object('a<', { contains_cursor = true }),
|
||||||
local c = Range.from_text_object('a(', { contains_cursor = true })
|
Range.from_text_object('a[', { contains_cursor = true }),
|
||||||
local d = Range.from_text_object('a{', { contains_cursor = true })
|
Range.from_text_object('a(', { contains_cursor = true }),
|
||||||
return Range.smallest { a, b, c, d }
|
Range.from_text_object('a{', { contains_cursor = true }),
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
function Range.find_nearest_quotes()
|
function Range.find_nearest_quotes()
|
||||||
local a = Range.from_text_object([[a']], { contains_cursor = true })
|
return Range.smallest {
|
||||||
if a ~= nil and a:is_empty() then a = nil end
|
Range.from_text_object([[a']], { contains_cursor = true }),
|
||||||
local b = Range.from_text_object([[a"]], { contains_cursor = true })
|
Range.from_text_object([[a"]], { contains_cursor = true }),
|
||||||
if b ~= nil and b:is_empty() then b = nil end
|
Range.from_text_object([[a`]], { contains_cursor = true }),
|
||||||
local c = Range.from_text_object([[a`]], { contains_cursor = true })
|
}
|
||||||
if c ~= nil and c:is_empty() then c = nil end
|
|
||||||
return Range.smallest { a, b, c }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param ranges (Range|nil)[]
|
---@param ranges (u.Range|nil)[]
|
||||||
function Range.smallest(ranges)
|
function Range.smallest(ranges)
|
||||||
---@type Range[]
|
---@type u.Range[]
|
||||||
local new_ranges = {}
|
ranges = vim.iter(ranges):filter(function(r) return r ~= nil and not r:is_empty() end):totable()
|
||||||
for _, r in pairs(ranges) do
|
|
||||||
if r ~= nil then table.insert(new_ranges, r) end
|
|
||||||
end
|
|
||||||
ranges = new_ranges
|
|
||||||
if #ranges == 0 then return nil end
|
if #ranges == 0 then return nil end
|
||||||
|
|
||||||
-- find smallest match
|
-- find smallest match
|
||||||
local max_start = ranges[1].start
|
local smallest = ranges[1]
|
||||||
local min_stop = ranges[1].stop
|
|
||||||
local result = ranges[1]
|
|
||||||
|
|
||||||
for _, r in ipairs(ranges) do
|
for _, r in ipairs(ranges) do
|
||||||
local start, stop = r.start, r.stop
|
local start, stop = r.start, r.stop
|
||||||
if start > max_start and stop < min_stop then
|
if start > smallest.start and stop < smallest.stop then smallest = r end
|
||||||
max_start = start
|
|
||||||
min_stop = stop
|
|
||||||
result = r
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
return smallest
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Range:clone() return Range.new(self.start:clone(), self.stop ~= nil and self.stop:clone() or nil, self.mode) end
|
function Range:clone() return Range.new(self.start:clone(), self.stop ~= nil and self.stop:clone() or nil, self.mode) end
|
||||||
@ -266,7 +256,7 @@ function Range:to_linewise()
|
|||||||
local r = self:clone()
|
local r = self:clone()
|
||||||
|
|
||||||
r.mode = 'V'
|
r.mode = 'V'
|
||||||
r.start.col = 0
|
r.start.col = 1
|
||||||
if r.stop ~= nil then r.stop.col = Pos.MAX_COL end
|
if r.stop ~= nil then r.stop.col = Pos.MAX_COL end
|
||||||
|
|
||||||
return r
|
return r
|
||||||
@ -298,19 +288,13 @@ function Range:trim_stop()
|
|||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param p Pos
|
---@param p u.Pos
|
||||||
function Range:contains(p) return not self:is_empty() and p >= self.start and p <= self.stop end
|
function Range:contains(p) return not self:is_empty() and p >= self.start and p <= self.stop end
|
||||||
|
|
||||||
---@return string[]
|
---@return string[]
|
||||||
function Range:lines()
|
function Range:lines()
|
||||||
if self:is_empty() then return {} end
|
if self:is_empty() then return {} end
|
||||||
|
return vim.fn.getregion(self.start:as_vim(), self.stop:as_vim(), { type = self.mode })
|
||||||
local lines = {}
|
|
||||||
for i = 0, self.stop.lnum - self.start.lnum do
|
|
||||||
local line = self:line0(i)
|
|
||||||
if line ~= nil then table.insert(lines, line.text()) end
|
|
||||||
end
|
|
||||||
return lines
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string
|
---@return string
|
||||||
@ -321,34 +305,17 @@ function Range:text() return vim.fn.join(self:lines(), '\n') end
|
|||||||
function Range:sub(i, j) return self:text():sub(i, j) end
|
function Range:sub(i, j) return self:text():sub(i, j) end
|
||||||
|
|
||||||
---@param l number
|
---@param l number
|
||||||
---@return { line: string; idx0: { start: number; stop: number; }; lnum: number; range: fun():Range; text: fun():string }|nil
|
---@return { line: string; idx0: { start: number; stop: number; }; lnum: number; range: fun():u.Range; text: fun():string }|nil
|
||||||
function Range:line0(l)
|
function Range:line(l)
|
||||||
if l < 0 then return self:line0(self:line_count() + l) end
|
if l < 0 then l = self:line_count() + l + 1 end
|
||||||
if l > self:line_count() then return end
|
if l > self:line_count() then return end
|
||||||
|
|
||||||
local line = vim.api.nvim_buf_get_lines(self.start.buf, self.start.lnum + l, self.start.lnum + l + 1, false)[1]
|
local line_indices = vim.fn.getregionpos(self.start:as_vim(), self.stop:as_vim(), { type = self.mode })
|
||||||
if line == nil then return end
|
local line_bounds = line_indices[l]
|
||||||
|
|
||||||
local start = 0
|
local start = Pos.new(unpack(line_bounds[1]))
|
||||||
local stop = #line - 1
|
local stop = Pos.new(unpack(line_bounds[2]))
|
||||||
if l == 0 then start = self.start.col end
|
return Range.new(start, stop)
|
||||||
if l == self.stop.lnum - self.start.lnum then stop = self.stop.col end
|
|
||||||
if stop == Pos.MAX_COL then stop = #line - 1 end
|
|
||||||
local lnum = self.start.lnum + l
|
|
||||||
|
|
||||||
return {
|
|
||||||
line = line,
|
|
||||||
idx0 = { start = start, stop = stop },
|
|
||||||
lnum = lnum,
|
|
||||||
range = function()
|
|
||||||
return Range.new(
|
|
||||||
Pos.new(self.start.buf, lnum, start, self.start.off),
|
|
||||||
Pos.new(self.start.buf, lnum, stop, self.stop.off),
|
|
||||||
'v'
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
text = function() return line:sub(start + 1, stop + 1) end,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param replacement nil|string|string[]
|
---@param replacement nil|string|string[]
|
||||||
@ -357,51 +324,55 @@ function Range:replace(replacement)
|
|||||||
if type(replacement) == 'string' then replacement = vim.fn.split(replacement, '\n') end
|
if type(replacement) == 'string' then replacement = vim.fn.split(replacement, '\n') end
|
||||||
|
|
||||||
local buf = self.start.buf
|
local buf = self.start.buf
|
||||||
-- convert to start-inclusive, stop-exclusive coordinates:
|
|
||||||
local start_lnum, stop_lnum = self.start.lnum, (self.stop and self.stop.lnum or self.start.lnum) + 1
|
|
||||||
local start_col, stop_col = self.start.col, (self.stop and self.stop.col or self.start.col) + 1
|
|
||||||
|
|
||||||
local replace_type = (self:is_empty() and 'insert') or (self.mode == 'v' and 'region') or 'lines'
|
local replace_type = (self:is_empty() and 'insert') or (self.mode == 'v' and 'region') or 'lines'
|
||||||
|
|
||||||
---@param alnum number
|
local function update_stop_non_linewise()
|
||||||
---@param acol number
|
|
||||||
---@param blnum number
|
|
||||||
---@param bcol number
|
|
||||||
local function set_text(alnum, acol, blnum, bcol, repl)
|
|
||||||
-- row indices are end-inclusive, and column indices are end-exclusive.
|
|
||||||
vim.api.nvim_buf_set_text(buf, alnum, acol, blnum, bcol, repl)
|
|
||||||
|
|
||||||
local new_last_line_num = self.start.lnum + #replacement - 1
|
local new_last_line_num = self.start.lnum + #replacement - 1
|
||||||
local new_last_col = #(replacement[#replacement] or '')
|
local new_last_col = #(replacement[#replacement] or '')
|
||||||
if new_last_line_num == start_lnum then new_last_col = new_last_col + start_col - 1 end
|
if new_last_line_num == self.start.lnum then new_last_col = new_last_col + self.start.col - 1 end
|
||||||
|
|
||||||
self.stop = Pos.new(buf, new_last_line_num, new_last_col)
|
self.stop = Pos.new(buf, new_last_line_num, new_last_col)
|
||||||
end
|
end
|
||||||
|
local function update_stop_linewise()
|
||||||
---@param alnum number
|
if #replacement == 0 then
|
||||||
---@param blnum number
|
|
||||||
local function set_lines(alnum, blnum, repl)
|
|
||||||
-- indexing is zero-based, end-exclusive
|
|
||||||
vim.api.nvim_buf_set_lines(buf, alnum, blnum, false, repl)
|
|
||||||
|
|
||||||
if #repl == 0 then
|
|
||||||
self.stop = nil
|
self.stop = nil
|
||||||
else
|
else
|
||||||
local new_last_line_num = start_lnum + #replacement - 1
|
local new_last_line_num = self.start.lnum - 1 + #replacement - 1
|
||||||
self.stop = Pos.new(self.start.buf, new_last_line_num, Pos.MAX_COL, self.stop.off)
|
self.stop = Pos.new(buf, new_last_line_num + 1, Pos.MAX_COL, self.stop.off)
|
||||||
end
|
end
|
||||||
self.mode = 'v'
|
self.mode = 'v'
|
||||||
end
|
end
|
||||||
|
|
||||||
if replace_type == 'insert' then
|
if replace_type == 'insert' then
|
||||||
set_text(start_lnum, start_col, start_lnum, start_col, replacement)
|
-- To insert text at a given `(row, column)` location, use `start_row =
|
||||||
|
-- end_row = row` and `start_col = end_col = col`.
|
||||||
|
vim.api.nvim_buf_set_text(
|
||||||
|
buf,
|
||||||
|
self.start.lnum - 1,
|
||||||
|
self.start.col - 1,
|
||||||
|
self.start.lnum - 1,
|
||||||
|
self.start.col - 1,
|
||||||
|
replacement
|
||||||
|
)
|
||||||
|
update_stop_non_linewise()
|
||||||
elseif replace_type == 'region' then
|
elseif replace_type == 'region' then
|
||||||
-- Fixup the bounds:
|
-- Fixup the bounds:
|
||||||
local last_line = vim.api.nvim_buf_get_lines(buf, stop_lnum - 1, stop_lnum, false)[1] or ''
|
local max_col = #self.stop:line()
|
||||||
local max_col = #last_line
|
|
||||||
set_text(start_lnum, start_col, stop_lnum - 1, math.min(stop_col, max_col), replacement)
|
-- Indexing is zero-based. Row indices are end-inclusive, and column indices
|
||||||
|
-- are end-exclusive.
|
||||||
|
vim.api.nvim_buf_set_text(
|
||||||
|
buf,
|
||||||
|
self.start.lnum - 1,
|
||||||
|
self.start.col - 1,
|
||||||
|
self.stop.lnum - 1,
|
||||||
|
math.min(self.stop.col, max_col),
|
||||||
|
replacement
|
||||||
|
)
|
||||||
|
update_stop_non_linewise()
|
||||||
elseif replace_type == 'lines' then
|
elseif replace_type == 'lines' then
|
||||||
set_lines(start_lnum, stop_lnum, replacement)
|
-- Indexing is zero-based, end-exclusive.
|
||||||
|
vim.api.nvim_buf_set_lines(buf, self.start.lnum - 1, self.stop.lnum, true, replacement)
|
||||||
|
update_stop_linewise()
|
||||||
else
|
else
|
||||||
error 'unreachable'
|
error 'unreachable'
|
||||||
end
|
end
|
||||||
@ -461,21 +432,11 @@ function Range:set_visual_selection()
|
|||||||
|
|
||||||
if vim.api.nvim_get_current_buf() ~= self.start.buf then vim.api.nvim_set_current_buf(self.start.buf) end
|
if vim.api.nvim_get_current_buf() ~= self.start.buf then vim.api.nvim_set_current_buf(self.start.buf) end
|
||||||
|
|
||||||
State.run(self.start.buf, function(s)
|
local curr_mode = vim.fn.mode()
|
||||||
s:track_mark 'a'
|
if curr_mode ~= self.mode then vim.fn.feedkeys(self.mode, 'x') end
|
||||||
s:track_mark 'b'
|
self.start:save_to_pos '.'
|
||||||
|
vim.fn.feedkeys('o', 'x')
|
||||||
self.start:save_to_mark 'a'
|
self.stop:save_to_pos '.'
|
||||||
self.stop:save_to_mark 'b'
|
|
||||||
local mode = self.mode
|
|
||||||
|
|
||||||
local normal_cmd_args = ''
|
|
||||||
if vim.api.nvim_get_mode().mode == 'n' then normal_cmd_args = normal_cmd_args .. mode end
|
|
||||||
normal_cmd_args = normal_cmd_args .. '`ao`b'
|
|
||||||
vim.cmd { cmd = 'normal', args = { normal_cmd_args }, bang = true }
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param group string
|
---@param group string
|
||||||
@ -490,33 +451,31 @@ function Range:highlight(group, opts)
|
|||||||
if not opts.on_macro and in_macro then return { clear = function() end } end
|
if not opts.on_macro and in_macro then return { clear = function() end } end
|
||||||
|
|
||||||
local ns = vim.api.nvim_create_namespace ''
|
local ns = vim.api.nvim_create_namespace ''
|
||||||
State.run(self.start.buf, function(s)
|
|
||||||
if not in_macro then s:track_winview() end
|
|
||||||
|
|
||||||
(vim.hl or vim.highlight).range(
|
local winview = vim.fn.winsaveview();
|
||||||
self.start.buf,
|
(vim.hl or vim.highlight).range(
|
||||||
ns,
|
self.start.buf,
|
||||||
group,
|
ns,
|
||||||
{ self.start.lnum, self.start.col },
|
group,
|
||||||
{ self.stop.lnum, self.stop.col },
|
{ self.start.lnum - 1, self.start.col - 1 },
|
||||||
{
|
{ self.stop.lnum - 1, self.stop.col - 1 },
|
||||||
inclusive = true,
|
{
|
||||||
priority = opts.priority,
|
inclusive = true,
|
||||||
regtype = self.mode,
|
priority = opts.priority,
|
||||||
}
|
timeout = opts.timeout,
|
||||||
)
|
regtype = self.mode,
|
||||||
|
}
|
||||||
return nil
|
)
|
||||||
end)
|
if not in_macro then vim.fn.winrestview(winview) end
|
||||||
vim.cmd.redraw()
|
vim.cmd.redraw()
|
||||||
|
|
||||||
local function clear()
|
return {
|
||||||
vim.api.nvim_buf_clear_namespace(self.start.buf, ns, self.start.lnum, self.stop.lnum + 1)
|
ns = ns,
|
||||||
vim.cmd.redraw()
|
clear = function()
|
||||||
end
|
vim.api.nvim_buf_clear_namespace(self.start.buf, ns, self.start.lnum - 1, self.stop.lnum)
|
||||||
if opts.timeout ~= nil then vim.defer_fn(clear, opts.timeout) end
|
vim.cmd.redraw()
|
||||||
|
end,
|
||||||
return { ns = ns, clear = clear }
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return Range
|
return Range
|
||||||
|
@ -21,7 +21,7 @@ end
|
|||||||
-- Renderer {{{
|
-- Renderer {{{
|
||||||
--- @alias RendererExtmark { id?: number; start: [number, number]; stop: [number, number]; opts: any; tag: any }
|
--- @alias RendererExtmark { id?: number; start: [number, number]; stop: [number, number]; opts: any; tag: any }
|
||||||
|
|
||||||
--- @class Renderer
|
--- @class u.Renderer
|
||||||
--- @field bufnr number
|
--- @field bufnr number
|
||||||
--- @field ns number
|
--- @field ns number
|
||||||
--- @field changedtick number
|
--- @field changedtick number
|
||||||
@ -377,7 +377,7 @@ end -- }}}
|
|||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- TreeBuilder {{{
|
-- TreeBuilder {{{
|
||||||
--- @class TreeBuilder
|
--- @class u.TreeBuilder
|
||||||
--- @field private nodes Node[]
|
--- @field private nodes Node[]
|
||||||
local TreeBuilder = {}
|
local TreeBuilder = {}
|
||||||
TreeBuilder.__index = TreeBuilder
|
TreeBuilder.__index = TreeBuilder
|
||||||
@ -389,7 +389,7 @@ function TreeBuilder.new()
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- @param nodes Tree
|
--- @param nodes Tree
|
||||||
--- @return TreeBuilder
|
--- @return u.TreeBuilder
|
||||||
function TreeBuilder:put(nodes)
|
function TreeBuilder:put(nodes)
|
||||||
table.insert(self.nodes, nodes)
|
table.insert(self.nodes, nodes)
|
||||||
return self
|
return self
|
||||||
@ -398,7 +398,7 @@ end
|
|||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param attributes? table<string, any>
|
--- @param attributes? table<string, any>
|
||||||
--- @param children? Node | Node[]
|
--- @param children? Node | Node[]
|
||||||
--- @return TreeBuilder
|
--- @return u.TreeBuilder
|
||||||
function TreeBuilder:put_h(name, attributes, children)
|
function TreeBuilder:put_h(name, attributes, children)
|
||||||
local tag = M.h(name, attributes, children)
|
local tag = M.h(name, attributes, children)
|
||||||
table.insert(self.nodes, tag)
|
table.insert(self.nodes, tag)
|
||||||
@ -406,7 +406,7 @@ function TreeBuilder:put_h(name, attributes, children)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- @param fn fun(TreeBuilder): any
|
--- @param fn fun(TreeBuilder): any
|
||||||
--- @return TreeBuilder
|
--- @return u.TreeBuilder
|
||||||
function TreeBuilder:nest(fn)
|
function TreeBuilder:nest(fn)
|
||||||
local nested_writer = TreeBuilder.new()
|
local nested_writer = TreeBuilder.new()
|
||||||
fn(nested_writer)
|
fn(nested_writer)
|
||||||
|
@ -1,61 +1,39 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function _normal(cmd) vim.cmd { cmd = 'normal', args = { cmd }, bang = true } end
|
local IS_REPEATING = false
|
||||||
|
--- @type function
|
||||||
|
local REPEAT_ACTION = nil
|
||||||
|
|
||||||
M.native_repeat = function() _normal '.' end
|
local function is_repeatable_last_mutator() return vim.b.changedtick <= (vim.b.my_changedtick or 0) end
|
||||||
M.native_undo = function() _normal 'u' end
|
|
||||||
|
|
||||||
local function update_ts() vim.b.tt_changedtick = vim.b.changedtick end
|
--- @param f fun()
|
||||||
|
function M.run_repeatable(f)
|
||||||
---@param cmd? string|fun():unknown
|
REPEAT_ACTION = f
|
||||||
function M.set(cmd)
|
REPEAT_ACTION()
|
||||||
update_ts()
|
vim.b.my_changedtick = vim.b.changedtick
|
||||||
if cmd ~= nil then vim.b.tt_repeatcmd = cmd end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function tt_was_last_repeatable()
|
function M.is_repeating() return IS_REPEATING end
|
||||||
local ts, tt_ts = vim.b.changedtick, vim.b.tt_changedtick
|
|
||||||
return tt_ts ~= nil and ts <= tt_ts
|
|
||||||
end
|
|
||||||
|
|
||||||
---@generic T
|
|
||||||
---@param cmd string|fun():T
|
|
||||||
---@return T
|
|
||||||
function M.run(cmd)
|
|
||||||
M.set(cmd)
|
|
||||||
local result = cmd()
|
|
||||||
update_ts()
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.do_repeat()
|
|
||||||
local tt_cmd = vim.b.tt_repeatcmd
|
|
||||||
if not tt_was_last_repeatable() or (type(tt_cmd) ~= 'function' and type(tt_cmd) ~= 'string') then
|
|
||||||
return M.native_repeat()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- execute the cached command:
|
|
||||||
local count = vim.api.nvim_get_vvar 'count1'
|
|
||||||
if type(tt_cmd) == 'string' then
|
|
||||||
_normal(count .. tt_cmd --[[@as string]])
|
|
||||||
else
|
|
||||||
local last_return
|
|
||||||
for _ = 1, count do
|
|
||||||
last_return = M.run(tt_cmd --[[@as fun():any]])
|
|
||||||
end
|
|
||||||
return last_return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.undo()
|
|
||||||
local tt_was_last_repeatable_before_undo = tt_was_last_repeatable()
|
|
||||||
M.native_undo()
|
|
||||||
if tt_was_last_repeatable_before_undo then update_ts() end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.setup()
|
function M.setup()
|
||||||
vim.keymap.set('n', '.', M.do_repeat)
|
vim.keymap.set('n', '.', function()
|
||||||
vim.keymap.set('n', 'u', M.undo)
|
IS_REPEATING = true
|
||||||
|
for _ = 1, vim.v.count1 do
|
||||||
|
if is_repeatable_last_mutator() and type(REPEAT_ACTION) == 'function' then
|
||||||
|
M.run_repeatable(REPEAT_ACTION)
|
||||||
|
else
|
||||||
|
vim.cmd { cmd = 'normal', args = { '.' }, bang = true }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
IS_REPEATING = false
|
||||||
|
end)
|
||||||
|
vim.keymap.set('n', 'u', function()
|
||||||
|
local was_repeatable_last_mutator = is_repeatable_last_mutator()
|
||||||
|
for _ = 1, vim.v.count1 do
|
||||||
|
vim.cmd { cmd = 'normal', args = { 'u' }, bang = true }
|
||||||
|
end
|
||||||
|
if was_repeatable_last_mutator then vim.b.my_changedtick = vim.b.changedtick end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
---@class State
|
|
||||||
---@field buf number
|
|
||||||
---@field registers table
|
|
||||||
---@field marks table
|
|
||||||
---@field positions table
|
|
||||||
---@field keymaps { mode: string; lhs: any, rhs: any, buffer?: number }[]
|
|
||||||
---@field global_options table<string, any>
|
|
||||||
---@field win_view vim.fn.winsaveview.ret|nil
|
|
||||||
local State = {}
|
|
||||||
|
|
||||||
---@param buf number
|
|
||||||
---@return State
|
|
||||||
function State.new(buf)
|
|
||||||
if buf == 0 then buf = vim.api.nvim_get_current_buf() end
|
|
||||||
local s = { buf = buf, registers = {}, marks = {}, positions = {}, keymaps = {}, global_options = {} }
|
|
||||||
setmetatable(s, { __index = State })
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
---@generic T
|
|
||||||
---@param buf number
|
|
||||||
---@param f fun(s: State):T
|
|
||||||
---@return T
|
|
||||||
function State.run(buf, f)
|
|
||||||
local s = State.new(buf)
|
|
||||||
local ok, result = pcall(f, s)
|
|
||||||
s:restore()
|
|
||||||
if not ok then error(result) end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param buf number
|
|
||||||
---@param f fun(s: State, callback: fun(): any):any
|
|
||||||
---@param callback fun():any
|
|
||||||
function State.run_async(buf, f, callback)
|
|
||||||
local s = State.new(buf)
|
|
||||||
f(s, function()
|
|
||||||
s:restore()
|
|
||||||
callback()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:track_keymap(mode, lhs)
|
|
||||||
local old =
|
|
||||||
-- Look up the mapping in buffer-local maps:
|
|
||||||
vim.iter(vim.api.nvim_buf_get_keymap(self.buf, mode)):find(function(map) return map.lhs == lhs end)
|
|
||||||
-- Look up the mapping in global maps:
|
|
||||||
or vim.iter(vim.api.nvim_get_keymap(mode)):find(function(map) return map.lhs == lhs end)
|
|
||||||
|
|
||||||
-- Did we find a mapping?
|
|
||||||
if old == nil then return end
|
|
||||||
|
|
||||||
-- Track it:
|
|
||||||
table.insert(self.keymaps, { mode = mode, lhs = lhs, rhs = old.rhs or old.callback, buffer = old.buffer })
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param reg string
|
|
||||||
function State:track_register(reg) self.registers[reg] = vim.fn.getreg(reg) end
|
|
||||||
|
|
||||||
---@param mark string
|
|
||||||
function State:track_mark(mark) self.marks[mark] = vim.api.nvim_buf_get_mark(self.buf, mark) end
|
|
||||||
|
|
||||||
---@param pos string
|
|
||||||
function State:track_pos(pos) self.positions[pos] = vim.fn.getpos(pos) end
|
|
||||||
|
|
||||||
---@param nm string
|
|
||||||
function State:track_global_option(nm) self.global_options[nm] = vim.go[nm] end
|
|
||||||
|
|
||||||
function State:track_winview() self.win_view = vim.fn.winsaveview() end
|
|
||||||
|
|
||||||
function State:restore()
|
|
||||||
for reg, val in pairs(self.registers) do
|
|
||||||
vim.fn.setreg(reg, val)
|
|
||||||
end
|
|
||||||
for mark, val in pairs(self.marks) do
|
|
||||||
vim.api.nvim_buf_set_mark(self.buf, mark, val[1], val[2], {})
|
|
||||||
end
|
|
||||||
for pos, val in pairs(self.positions) do
|
|
||||||
vim.fn.setpos(pos, val)
|
|
||||||
end
|
|
||||||
for _, map in ipairs(self.keymaps) do
|
|
||||||
vim.keymap.set(map.mode, map.lhs, map.rhs, { buffer = map.buffer })
|
|
||||||
end
|
|
||||||
for nm, val in pairs(self.global_options) do
|
|
||||||
vim.go[nm] = val
|
|
||||||
end
|
|
||||||
if self.win_view ~= nil then vim.fn.winrestview(self.win_view) end
|
|
||||||
end
|
|
||||||
|
|
||||||
return State
|
|
@ -6,7 +6,7 @@ M.debug = false
|
|||||||
-- class Signal
|
-- class Signal
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
--- @class Signal
|
--- @class u.Signal
|
||||||
--- @field name? string
|
--- @field name? string
|
||||||
--- @field private changing boolean
|
--- @field private changing boolean
|
||||||
--- @field private value any
|
--- @field private value any
|
||||||
@ -18,7 +18,7 @@ Signal.__index = Signal
|
|||||||
|
|
||||||
--- @param value any
|
--- @param value any
|
||||||
--- @param name? string
|
--- @param name? string
|
||||||
--- @return Signal
|
--- @return u.Signal
|
||||||
function Signal:new(value, name)
|
function Signal:new(value, name)
|
||||||
local obj = setmetatable({
|
local obj = setmetatable({
|
||||||
name = name,
|
name = name,
|
||||||
@ -83,7 +83,7 @@ function Signal:schedule_update(fn) self:schedule_set(fn(self.value)) end
|
|||||||
|
|
||||||
--- @generic U
|
--- @generic U
|
||||||
--- @param fn fun(value: T): U
|
--- @param fn fun(value: T): U
|
||||||
--- @return Signal --<U>
|
--- @return u.Signal --<U>
|
||||||
function Signal:map(fn)
|
function Signal:map(fn)
|
||||||
local mapped_signal = M.create_memo(function()
|
local mapped_signal = M.create_memo(function()
|
||||||
local value = self:get()
|
local value = self:get()
|
||||||
@ -92,13 +92,13 @@ function Signal:map(fn)
|
|||||||
return mapped_signal
|
return mapped_signal
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @return Signal
|
--- @return u.Signal
|
||||||
function Signal:clone()
|
function Signal:clone()
|
||||||
return self:map(function(x) return x end)
|
return self:map(function(x) return x end)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param fn fun(value: T): boolean
|
--- @param fn fun(value: T): boolean
|
||||||
--- @return Signal -- <T>
|
--- @return u.Signal -- <T>
|
||||||
function Signal:filter(fn)
|
function Signal:filter(fn)
|
||||||
local filtered_signal = M.create_signal(nil, self.name and self.name .. ':filtered' or nil)
|
local filtered_signal = M.create_signal(nil, self.name and self.name .. ':filtered' or nil)
|
||||||
local unsubscribe_from_self = self:subscribe(function(value)
|
local unsubscribe_from_self = self:subscribe(function(value)
|
||||||
@ -109,7 +109,7 @@ function Signal:filter(fn)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- @param ms number
|
--- @param ms number
|
||||||
--- @return Signal -- <T>
|
--- @return u.Signal -- <T>
|
||||||
function Signal:debounce(ms)
|
function Signal:debounce(ms)
|
||||||
local function set_timeout(timeout, callback)
|
local function set_timeout(timeout, callback)
|
||||||
local timer = (vim.uv or vim.loop).new_timer()
|
local timer = (vim.uv or vim.loop).new_timer()
|
||||||
@ -123,15 +123,15 @@ function Signal:debounce(ms)
|
|||||||
|
|
||||||
local filtered = M.create_signal(self.value, self.name and self.name .. ':debounced' or nil)
|
local filtered = M.create_signal(self.value, self.name and self.name .. ':debounced' or nil)
|
||||||
|
|
||||||
--- @type {
|
--- @diagnostic disable-next-line: undefined-doc-name
|
||||||
-- queued: { value: T, ts: number }[]
|
--- @type { queued: { value: T, ts: number }[]; timer?: uv_timer_t; }
|
||||||
-- timer?: uv_timer_t
|
|
||||||
-- }
|
|
||||||
local state = { queued = {}, timer = nil }
|
local state = { queued = {}, timer = nil }
|
||||||
local function clear_timeout()
|
local function clear_timeout()
|
||||||
if state.timer == nil then return end
|
if state.timer == nil then return end
|
||||||
pcall(function()
|
pcall(function()
|
||||||
|
--- @diagnostic disable-next-line: undefined-field
|
||||||
state.timer:stop()
|
state.timer:stop()
|
||||||
|
--- @diagnostic disable-next-line: undefined-field
|
||||||
state.timer:close()
|
state.timer:close()
|
||||||
end)
|
end)
|
||||||
state.timer = nil
|
state.timer = nil
|
||||||
@ -198,13 +198,13 @@ end
|
|||||||
|
|
||||||
CURRENT_CONTEXT = nil
|
CURRENT_CONTEXT = nil
|
||||||
|
|
||||||
--- @class ExecutionContext
|
--- @class u.ExecutionContext
|
||||||
--- @field signals table<Signal, boolean>
|
--- @field signals table<u.Signal, boolean>
|
||||||
local ExecutionContext = {}
|
local ExecutionContext = {}
|
||||||
M.ExecutionContext = ExecutionContext
|
M.ExecutionContext = ExecutionContext
|
||||||
ExecutionContext.__index = ExecutionContext
|
ExecutionContext.__index = ExecutionContext
|
||||||
|
|
||||||
--- @return ExecutionContext
|
--- @return u.ExecutionContext
|
||||||
function ExecutionContext:new()
|
function ExecutionContext:new()
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
signals = {},
|
signals = {},
|
||||||
@ -215,7 +215,7 @@ end
|
|||||||
function ExecutionContext.current() return CURRENT_CONTEXT end
|
function ExecutionContext.current() return CURRENT_CONTEXT end
|
||||||
|
|
||||||
--- @param fn function
|
--- @param fn function
|
||||||
--- @param ctx ExecutionContext
|
--- @param ctx u.ExecutionContext
|
||||||
function ExecutionContext:run(fn, ctx)
|
function ExecutionContext:run(fn, ctx)
|
||||||
local oldCtx = CURRENT_CONTEXT
|
local oldCtx = CURRENT_CONTEXT
|
||||||
CURRENT_CONTEXT = ctx
|
CURRENT_CONTEXT = ctx
|
||||||
@ -258,14 +258,14 @@ end
|
|||||||
|
|
||||||
--- @param value any
|
--- @param value any
|
||||||
--- @param name? string
|
--- @param name? string
|
||||||
--- @return Signal
|
--- @return u.Signal
|
||||||
function M.create_signal(value, name) return Signal:new(value, name) end
|
function M.create_signal(value, name) return Signal:new(value, name) end
|
||||||
|
|
||||||
--- @param fn function
|
--- @param fn function
|
||||||
--- @param name? string
|
--- @param name? string
|
||||||
--- @return Signal
|
--- @return u.Signal
|
||||||
function M.create_memo(fn, name)
|
function M.create_memo(fn, name)
|
||||||
--- @type Signal
|
--- @type u.Signal
|
||||||
local result
|
local result
|
||||||
local unsubscribe = M.create_effect(function()
|
local unsubscribe = M.create_effect(function()
|
||||||
local value = fn()
|
local value = fn()
|
||||||
|
@ -6,7 +6,7 @@ local M = {}
|
|||||||
|
|
||||||
---@alias QfItem { col: number, filename: string, kind: string, lnum: number, text: string }
|
---@alias QfItem { col: number, filename: string, kind: string, lnum: number, text: string }
|
||||||
---@alias KeyMaps table<string, fun(): any | string> }
|
---@alias KeyMaps table<string, fun(): any | string> }
|
||||||
---@alias CmdArgs { args: string; bang: boolean; count: number; fargs: string[]; line1: number; line2: number; mods: string; name: string; range: 0|1|2; reg: string; smods: any; info: Range|nil }
|
---@alias CmdArgs { args: string; bang: boolean; count: number; fargs: string[]; line1: number; line2: number; mods: string; name: string; range: 0|1|2; reg: string; smods: any; info: u.Range|nil }
|
||||||
|
|
||||||
--- @generic T
|
--- @generic T
|
||||||
--- @param x `T`
|
--- @param x `T`
|
||||||
@ -50,7 +50,7 @@ function M.ucmd(name, cmd, opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param key_seq string
|
---@param key_seq string
|
||||||
---@param fn fun(key_seq: string):Range|Pos|nil
|
---@param fn fun(key_seq: string):u.Range|u.Pos|nil
|
||||||
---@param opts? { buffer: number|nil }
|
---@param opts? { buffer: number|nil }
|
||||||
function M.define_text_object(key_seq, fn, opts)
|
function M.define_text_object(key_seq, fn, opts)
|
||||||
local Range = require 'u.range'
|
local Range = require 'u.range'
|
||||||
@ -64,7 +64,7 @@ function M.define_text_object(key_seq, fn, opts)
|
|||||||
if Range.is(range_or_pos) and range_or_pos:is_empty() then range_or_pos = range_or_pos.start end
|
if Range.is(range_or_pos) and range_or_pos:is_empty() then range_or_pos = range_or_pos.start end
|
||||||
|
|
||||||
if Range.is(range_or_pos) then
|
if Range.is(range_or_pos) then
|
||||||
local range = range_or_pos --[[@as Range]]
|
local range = range_or_pos --[[@as u.Range]]
|
||||||
range:set_visual_selection()
|
range:set_visual_selection()
|
||||||
else
|
else
|
||||||
vim.cmd { cmd = 'normal', args = { '<Esc>' }, bang = true }
|
vim.cmd { cmd = 'normal', args = { '<Esc>' }, bang = true }
|
||||||
@ -73,8 +73,6 @@ function M.define_text_object(key_seq, fn, opts)
|
|||||||
vim.keymap.set({ 'x' }, key_seq, handle_visual, opts and { buffer = opts.buffer } or nil)
|
vim.keymap.set({ 'x' }, key_seq, handle_visual, opts and { buffer = opts.buffer } or nil)
|
||||||
|
|
||||||
local function handle_normal()
|
local function handle_normal()
|
||||||
local State = require 'u.state'
|
|
||||||
|
|
||||||
-- enter visual mode:
|
-- enter visual mode:
|
||||||
vim.cmd { cmd = 'normal', args = { 'v' }, bang = true }
|
vim.cmd { cmd = 'normal', args = { 'v' }, bang = true }
|
||||||
|
|
||||||
@ -85,40 +83,21 @@ function M.define_text_object(key_seq, fn, opts)
|
|||||||
if Range.is(range_or_pos) then
|
if Range.is(range_or_pos) then
|
||||||
range_or_pos:set_visual_selection()
|
range_or_pos:set_visual_selection()
|
||||||
elseif Pos.is(range_or_pos) then
|
elseif Pos.is(range_or_pos) then
|
||||||
local p = range_or_pos --[[@as Pos]]
|
local p = range_or_pos --[[@as u.Pos]]
|
||||||
State.run(0, function(s)
|
|
||||||
s:track_global_option 'eventignore'
|
|
||||||
vim.go.eventignore = 'all'
|
|
||||||
|
|
||||||
-- insert a single space, so we can select it:
|
local original_eventignore = vim.go.eventignore
|
||||||
vim.api.nvim_buf_set_text(0, p.lnum, p.col, p.lnum, p.col, { ' ' })
|
vim.go.eventignore = 'all'
|
||||||
-- select the space:
|
-- insert a single space, so we can select it:
|
||||||
Range.new(p, p, 'v'):set_visual_selection()
|
vim.api.nvim_buf_set_text(0, p.lnum, p.col, p.lnum, p.col, { ' ' })
|
||||||
end)
|
vim.go.eventignore = original_eventignore
|
||||||
|
|
||||||
|
-- select the space:
|
||||||
|
Range.new(p, p, 'v'):set_visual_selection()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
vim.keymap.set({ 'o' }, key_seq, handle_normal, opts and { buffer = opts.buffer } or nil)
|
vim.keymap.set({ 'o' }, key_seq, handle_normal, opts and { buffer = opts.buffer } or nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type fun(): nil|(fun():any)
|
|
||||||
local __U__RepeatableOpFunc_rhs = nil
|
|
||||||
|
|
||||||
--- This is the global utility function used for operatorfunc
|
|
||||||
--- in repeatablemap
|
|
||||||
---@type nil|fun(range: Range): fun():any|nil
|
|
||||||
-- selene: allow(unused_variable)
|
|
||||||
function __U__RepeatableOpFunc()
|
|
||||||
if __U__RepeatableOpFunc_rhs ~= nil then __U__RepeatableOpFunc_rhs() end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.repeatablemap(mode, lhs, rhs, opts)
|
|
||||||
vim.keymap.set(mode, lhs, function()
|
|
||||||
__U__RepeatableOpFunc_rhs = rhs
|
|
||||||
vim.o.operatorfunc = 'v:lua.__U__RepeatableOpFunc'
|
|
||||||
return 'g@ '
|
|
||||||
end, vim.tbl_extend('force', opts or {}, { expr = true }))
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.get_editor_dimensions() return { width = vim.go.columns, height = vim.go.lines } end
|
function M.get_editor_dimensions() return { width = vim.go.columns, height = vim.go.lines } end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
19
lux.toml
Normal file
19
lux.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package = "u.nvim"
|
||||||
|
version = "0.1.0"
|
||||||
|
lua = ">=5.1"
|
||||||
|
|
||||||
|
[description]
|
||||||
|
summary = ""
|
||||||
|
maintainer = "jrop"
|
||||||
|
labels = [ "library", "neovim", "neovim-plugin", "range", "utility" ]
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Add your dependencies here
|
||||||
|
# `busted = ">=2.0"`
|
||||||
|
|
||||||
|
[run]
|
||||||
|
args = [ "src/main.lua" ]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
type = "builtin"
|
@ -4,12 +4,12 @@ local withbuf = loadfile './spec/withbuf.lua'()
|
|||||||
describe('Pos', function()
|
describe('Pos', function()
|
||||||
it('get a char from a given position', function()
|
it('get a char from a given position', function()
|
||||||
withbuf({ 'asdf', 'bleh', 'a', '', 'goo' }, function()
|
withbuf({ 'asdf', 'bleh', 'a', '', 'goo' }, function()
|
||||||
assert.are.same('a', Pos.new(nil, 0, 0):char())
|
assert.are.same('a', Pos.new(nil, 1, 1):char())
|
||||||
assert.are.same('d', Pos.new(nil, 0, 2):char())
|
assert.are.same('d', Pos.new(nil, 1, 3):char())
|
||||||
assert.are.same('f', Pos.new(nil, 0, 3):char())
|
assert.are.same('f', Pos.new(nil, 1, 4):char())
|
||||||
assert.are.same('a', Pos.new(nil, 2, 0):char())
|
assert.are.same('a', Pos.new(nil, 3, 1):char())
|
||||||
assert.are.same('', Pos.new(nil, 3, 0):char())
|
assert.are.same('', Pos.new(nil, 4, 1):char())
|
||||||
assert.are.same('o', Pos.new(nil, 4, 2):char())
|
assert.are.same('o', Pos.new(nil, 5, 3):char())
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -23,47 +23,47 @@ describe('Pos', function()
|
|||||||
it('get the next position', function()
|
it('get the next position', function()
|
||||||
withbuf({ 'asdf', 'bleh', 'a', '', 'goo' }, function()
|
withbuf({ 'asdf', 'bleh', 'a', '', 'goo' }, function()
|
||||||
-- line 1: a => s
|
-- line 1: a => s
|
||||||
assert.are.same(Pos.new(nil, 0, 1), Pos.new(nil, 0, 0):next())
|
assert.are.same(Pos.new(nil, 1, 2), Pos.new(nil, 1, 1):next())
|
||||||
-- line 1: d => f
|
-- line 1: d => f
|
||||||
assert.are.same(Pos.new(nil, 0, 3), Pos.new(nil, 0, 2):next())
|
assert.are.same(Pos.new(nil, 1, 4), Pos.new(nil, 1, 3):next())
|
||||||
-- line 1 => 2
|
-- line 1 => 2
|
||||||
assert.are.same(Pos.new(nil, 1, 0), Pos.new(nil, 0, 3):next())
|
assert.are.same(Pos.new(nil, 2, 1), Pos.new(nil, 1, 4):next())
|
||||||
-- line 3 => 4
|
-- line 3 => 4
|
||||||
assert.are.same(Pos.new(nil, 3, 0), Pos.new(nil, 2, 0):next())
|
assert.are.same(Pos.new(nil, 4, 1), Pos.new(nil, 3, 1):next())
|
||||||
-- line 4 => 5
|
-- line 4 => 5
|
||||||
assert.are.same(Pos.new(nil, 4, 0), Pos.new(nil, 3, 0):next())
|
assert.are.same(Pos.new(nil, 5, 1), Pos.new(nil, 4, 1):next())
|
||||||
-- end returns nil
|
-- end returns nil
|
||||||
assert.are.same(nil, Pos.new(nil, 4, 2):next())
|
assert.are.same(nil, Pos.new(nil, 5, 3):next())
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('get the previous position', function()
|
it('get the previous position', function()
|
||||||
withbuf({ 'asdf', 'bleh', 'a', '', 'goo' }, function()
|
withbuf({ 'asdf', 'bleh', 'a', '', 'goo' }, function()
|
||||||
-- line 1: s => a
|
-- line 1: s => a
|
||||||
assert.are.same(Pos.new(nil, 0, 0), Pos.new(nil, 0, 1):next(-1))
|
assert.are.same(Pos.new(nil, 1, 1), Pos.new(nil, 1, 2):next(-1))
|
||||||
-- line 1: f => d
|
-- line 1: f => d
|
||||||
assert.are.same(Pos.new(nil, 0, 2), Pos.new(nil, 0, 3):next(-1))
|
assert.are.same(Pos.new(nil, 1, 3), Pos.new(nil, 1, 4):next(-1))
|
||||||
-- line 2 => 1
|
-- line 2 => 1
|
||||||
assert.are.same(Pos.new(nil, 0, 3), Pos.new(nil, 1, 0):next(-1))
|
assert.are.same(Pos.new(nil, 1, 4), Pos.new(nil, 2, 1):next(-1))
|
||||||
-- line 4 => 3
|
-- line 4 => 3
|
||||||
assert.are.same(Pos.new(nil, 2, 0), Pos.new(nil, 3, 0):next(-1))
|
assert.are.same(Pos.new(nil, 3, 1), Pos.new(nil, 4, 1):next(-1))
|
||||||
-- line 5 => 4
|
-- line 5 => 4
|
||||||
assert.are.same(Pos.new(nil, 3, 0), Pos.new(nil, 4, 0):next(-1))
|
assert.are.same(Pos.new(nil, 4, 1), Pos.new(nil, 5, 1):next(-1))
|
||||||
-- beginning returns nil
|
-- beginning returns nil
|
||||||
assert.are.same(nil, Pos.new(nil, 0, 0):next(-1))
|
assert.are.same(nil, Pos.new(nil, 1, 1):next(-1))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('find matching brackets', function()
|
it('find matching brackets', function()
|
||||||
withbuf({ 'asdf ({} def <[{}]>) ;lkj' }, function()
|
withbuf({ 'asdf ({} def <[{}]>) ;lkj' }, function()
|
||||||
-- outer parens are matched:
|
-- outer parens are matched:
|
||||||
assert.are.same(Pos.new(nil, 0, 19), Pos.new(nil, 0, 5):find_match())
|
assert.are.same(Pos.new(nil, 1, 20), Pos.new(nil, 1, 6):find_match())
|
||||||
-- outer parens are matched (backward):
|
-- outer parens are matched (backward):
|
||||||
assert.are.same(Pos.new(nil, 0, 5), Pos.new(nil, 0, 19):find_match())
|
assert.are.same(Pos.new(nil, 1, 6), Pos.new(nil, 1, 20):find_match())
|
||||||
-- no potential match returns nil
|
-- no potential match returns nil
|
||||||
assert.are.same(nil, Pos.new(nil, 0, 0):find_match())
|
assert.are.same(nil, Pos.new(nil, 1, 1):find_match())
|
||||||
-- watchdog expires before an otherwise valid match is found:
|
-- watchdog expires before an otherwise valid match is found:
|
||||||
assert.are.same(nil, Pos.new(nil, 0, 5):find_match(2))
|
assert.are.same(nil, Pos.new(nil, 1, 6):find_match(2))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
@ -28,7 +28,7 @@ describe('Range', function()
|
|||||||
|
|
||||||
it('get from positions: v in single line', function()
|
it('get from positions: v in single line', function()
|
||||||
withbuf({ 'line one', 'and line two' }, function()
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 0, 3), 'v')
|
local range = Range.new(Pos.new(nil, 1, 2), Pos.new(nil, 1, 4), 'v')
|
||||||
local lines = range:lines()
|
local lines = range:lines()
|
||||||
assert.are.same({ 'ine' }, lines)
|
assert.are.same({ 'ine' }, lines)
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ describe('Range', function()
|
|||||||
|
|
||||||
it('get from positions: v across multiple lines', function()
|
it('get from positions: v across multiple lines', function()
|
||||||
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 1, 4), Pos.new(nil, 2, 4), 'v')
|
local range = Range.new(Pos.new(nil, 2, 5), Pos.new(nil, 3, 5), 'v')
|
||||||
local lines = range:lines()
|
local lines = range:lines()
|
||||||
assert.are.same({ 'quick brown fox', 'jumps' }, lines)
|
assert.are.same({ 'quick brown fox', 'jumps' }, lines)
|
||||||
end)
|
end)
|
||||||
@ -47,7 +47,7 @@ describe('Range', function()
|
|||||||
|
|
||||||
it('get from positions: V', function()
|
it('get from positions: V', function()
|
||||||
withbuf({ 'line one', 'and line two' }, function()
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, Pos.MAX_COL), 'V')
|
local range = Range.new(Pos.new(nil, 1, 1), Pos.new(nil, 1, Pos.MAX_COL), 'V')
|
||||||
local lines = range:lines()
|
local lines = range:lines()
|
||||||
assert.are.same({ 'line one' }, lines)
|
assert.are.same({ 'line one' }, lines)
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ describe('Range', function()
|
|||||||
|
|
||||||
it('get from positions: V across multiple lines', function()
|
it('get from positions: V across multiple lines', function()
|
||||||
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 1, 0), Pos.new(nil, 2, Pos.MAX_COL), 'V')
|
local range = Range.new(Pos.new(nil, 2, 1), Pos.new(nil, 3, Pos.MAX_COL), 'V')
|
||||||
local lines = range:lines()
|
local lines = range:lines()
|
||||||
assert.are.same({ 'the quick brown fox', 'jumps over a lazy dog' }, lines)
|
assert.are.same({ 'the quick brown fox', 'jumps over a lazy dog' }, lines)
|
||||||
end)
|
end)
|
||||||
@ -88,7 +88,7 @@ describe('Range', function()
|
|||||||
|
|
||||||
it('replace within line', function()
|
it('replace within line', function()
|
||||||
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 1, 4), Pos.new(nil, 1, 8), 'v')
|
local range = Range.new(Pos.new(nil, 2, 5), Pos.new(nil, 2, 9), 'v')
|
||||||
range:replace 'quack'
|
range:replace 'quack'
|
||||||
|
|
||||||
local text = Range.from_line(nil, 1):text()
|
local text = Range.from_line(nil, 1):text()
|
||||||
@ -98,7 +98,7 @@ describe('Range', function()
|
|||||||
|
|
||||||
it('delete within line', function()
|
it('delete within line', function()
|
||||||
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 1, 4), Pos.new(nil, 1, 9), 'v')
|
local range = Range.new(Pos.new(nil, 2, 5), Pos.new(nil, 2, 10), 'v')
|
||||||
range:replace ''
|
range:replace ''
|
||||||
|
|
||||||
local text = Range.from_line(nil, 1):text()
|
local text = Range.from_line(nil, 1):text()
|
||||||
@ -106,7 +106,7 @@ describe('Range', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 1, 4), Pos.new(nil, 1, 9), 'v')
|
local range = Range.new(Pos.new(nil, 2, 5), Pos.new(nil, 2, 10), 'v')
|
||||||
range:replace(nil)
|
range:replace(nil)
|
||||||
|
|
||||||
local text = Range.from_line(nil, 1):text()
|
local text = Range.from_line(nil, 1):text()
|
||||||
@ -116,7 +116,7 @@ describe('Range', function()
|
|||||||
|
|
||||||
it('replace across multiple lines: v', function()
|
it('replace across multiple lines: v', function()
|
||||||
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
withbuf({ 'pre line', 'the quick brown fox', 'jumps over a lazy dog', 'post line' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 1, 4), Pos.new(nil, 2, 4), 'v')
|
local range = Range.new(Pos.new(nil, 2, 5), Pos.new(nil, 3, 5), 'v')
|
||||||
range:replace 'plane flew'
|
range:replace 'plane flew'
|
||||||
|
|
||||||
local lines = Range.from_buf_text():lines()
|
local lines = Range.from_buf_text():lines()
|
||||||
@ -258,22 +258,18 @@ describe('Range', function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('line0', function()
|
it('line', function()
|
||||||
withbuf({
|
withbuf({
|
||||||
'this is a {',
|
'this is a {',
|
||||||
'block',
|
'block',
|
||||||
'} here',
|
'} here',
|
||||||
}, function()
|
}, function()
|
||||||
local range = Range.new(Pos.new(0, 0, 5), Pos.new(0, 1, 4), 'v')
|
local range = Range.new(Pos.new(0, 1, 6), Pos.new(0, 2, 5), 'v')
|
||||||
local lfirst = range:line0(0)
|
local lfirst = assert(range:line(1), 'lfirst null')
|
||||||
assert.are.same(5, lfirst.idx0.start)
|
assert.are.same('is a {', lfirst:text())
|
||||||
assert.are.same(10, lfirst.idx0.stop)
|
assert.are.same(Pos.new(0, 1, 6), lfirst.start)
|
||||||
assert.are.same(0, lfirst.lnum)
|
assert.are.same(Pos.new(0, 1, 11), lfirst.stop)
|
||||||
assert.are.same('is a {', lfirst.text())
|
assert.are.same('block', range:line(2):text())
|
||||||
assert.are.same('is a {', lfirst.range():text())
|
|
||||||
assert.are.same(Pos.new(0, 0, 5), lfirst.range().start)
|
|
||||||
assert.are.same(Pos.new(0, 0, 10), lfirst.range().stop)
|
|
||||||
assert.are.same('block', range:line0(1).text())
|
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -297,16 +293,16 @@ describe('Range', function()
|
|||||||
vim.cmd.normal 'v' -- enter visual mode
|
vim.cmd.normal 'v' -- enter visual mode
|
||||||
vim.cmd.normal 'l' -- select one character to the right
|
vim.cmd.normal 'l' -- select one character to the right
|
||||||
local range = Range.from_vtext()
|
local range = Range.from_vtext()
|
||||||
assert.are.same(range.start, Pos.new(nil, 0, 2))
|
assert.are.same(range.start, Pos.new(nil, 1, 3))
|
||||||
assert.are.same(range.stop, Pos.new(nil, 0, 3))
|
assert.are.same(range.stop, Pos.new(nil, 1, 4))
|
||||||
assert.are.same(range.mode, 'v')
|
assert.are.same(range.mode, 'v')
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('from_op_func', function()
|
it('from_op_func', function()
|
||||||
withbuf({ 'line one', 'and line two' }, function()
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
local a = Pos.new(nil, 0, 0)
|
local a = Pos.new(nil, 1, 1)
|
||||||
local b = Pos.new(nil, 1, 1)
|
local b = Pos.new(nil, 2, 2)
|
||||||
a:save_to_pos "'["
|
a:save_to_pos "'["
|
||||||
b:save_to_pos "']"
|
b:save_to_pos "']"
|
||||||
|
|
||||||
@ -317,7 +313,7 @@ describe('Range', function()
|
|||||||
|
|
||||||
range = Range.from_op_func 'line'
|
range = Range.from_op_func 'line'
|
||||||
assert.are.same(range.start, a)
|
assert.are.same(range.start, a)
|
||||||
assert.are.same(range.stop, Pos.new(nil, 1, Pos.MAX_COL))
|
assert.are.same(range.stop, Pos.new(nil, 2, Pos.MAX_COL))
|
||||||
assert.are.same(range.mode, 'V')
|
assert.are.same(range.mode, 'V')
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
@ -325,8 +321,8 @@ describe('Range', function()
|
|||||||
it('from_cmd_args', function()
|
it('from_cmd_args', function()
|
||||||
local args = { range = 1 }
|
local args = { range = 1 }
|
||||||
withbuf({ 'line one', 'and line two' }, function()
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
local a = Pos.new(nil, 0, 0)
|
local a = Pos.new(nil, 1, 1)
|
||||||
local b = Pos.new(nil, 1, 1)
|
local b = Pos.new(nil, 2, 2)
|
||||||
a:save_to_pos "'<"
|
a:save_to_pos "'<"
|
||||||
b:save_to_pos "'>"
|
b:save_to_pos "'>"
|
||||||
|
|
||||||
@ -341,25 +337,25 @@ describe('Range', function()
|
|||||||
withbuf({ [[the "quick" brown fox]] }, function()
|
withbuf({ [[the "quick" brown fox]] }, function()
|
||||||
vim.fn.setpos('.', { 0, 1, 5, 0 })
|
vim.fn.setpos('.', { 0, 1, 5, 0 })
|
||||||
local range = Range.find_nearest_quotes()
|
local range = Range.find_nearest_quotes()
|
||||||
assert.are.same(range.start, Pos.new(nil, 0, 4))
|
assert.are.same(range.start, Pos.new(nil, 1, 5))
|
||||||
assert.are.same(range.stop, Pos.new(nil, 0, 10))
|
assert.are.same(range.stop, Pos.new(nil, 1, 11))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
withbuf({ [[the 'quick' brown fox]] }, function()
|
withbuf({ [[the 'quick' brown fox]] }, function()
|
||||||
vim.fn.setpos('.', { 0, 1, 5, 0 })
|
vim.fn.setpos('.', { 0, 1, 5, 0 })
|
||||||
local range = Range.find_nearest_quotes()
|
local range = Range.find_nearest_quotes()
|
||||||
assert.are.same(range.start, Pos.new(nil, 0, 4))
|
assert.are.same(range.start, Pos.new(nil, 1, 5))
|
||||||
assert.are.same(range.stop, Pos.new(nil, 0, 10))
|
assert.are.same(range.stop, Pos.new(nil, 1, 11))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('smallest', function()
|
it('smallest', function()
|
||||||
local r1 = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 0, 3), 'v')
|
local r1 = Range.new(Pos.new(nil, 1, 2), Pos.new(nil, 1, 4), 'v')
|
||||||
local r2 = Range.new(Pos.new(nil, 0, 2), Pos.new(nil, 0, 4), 'v')
|
local r2 = Range.new(Pos.new(nil, 1, 3), Pos.new(nil, 1, 5), 'v')
|
||||||
local r3 = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, 5), 'v')
|
local r3 = Range.new(Pos.new(nil, 1, 1), Pos.new(nil, 1, 6), 'v')
|
||||||
local smallest = Range.smallest { r1, r2, r3 }
|
local smallest = Range.smallest { r1, r2, r3 }
|
||||||
assert.are.same(smallest.start, Pos.new(nil, 0, 1))
|
assert.are.same(smallest.start, Pos.new(nil, 1, 2))
|
||||||
assert.are.same(smallest.stop, Pos.new(nil, 0, 3))
|
assert.are.same(smallest.stop, Pos.new(nil, 1, 4))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('clone', function()
|
it('clone', function()
|
||||||
@ -381,9 +377,9 @@ describe('Range', function()
|
|||||||
|
|
||||||
it('to_linewise()', function()
|
it('to_linewise()', function()
|
||||||
withbuf({ 'line one', 'and line two' }, function()
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 1, 3), 'v')
|
local range = Range.new(Pos.new(nil, 1, 2), Pos.new(nil, 2, 4), 'v')
|
||||||
local linewise_range = range:to_linewise()
|
local linewise_range = range:to_linewise()
|
||||||
assert.are.same(linewise_range.start.col, 0)
|
assert.are.same(linewise_range.start.col, 1)
|
||||||
assert.are.same(linewise_range.stop.col, Pos.MAX_COL)
|
assert.are.same(linewise_range.stop.col, Pos.MAX_COL)
|
||||||
assert.are.same(linewise_range.mode, 'V')
|
assert.are.same(linewise_range.mode, 'V')
|
||||||
end)
|
end)
|
||||||
@ -391,56 +387,56 @@ describe('Range', function()
|
|||||||
|
|
||||||
it('is_empty', function()
|
it('is_empty', function()
|
||||||
withbuf({ 'line one', 'and line two' }, function()
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 0, 0), nil, 'v')
|
local range = Range.new(Pos.new(nil, 1, 1), nil, 'v')
|
||||||
assert.is_true(range:is_empty())
|
assert.is_true(range:is_empty())
|
||||||
|
|
||||||
local range2 = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, 1), 'v')
|
local range2 = Range.new(Pos.new(nil, 1, 1), Pos.new(nil, 1, 2), 'v')
|
||||||
assert.is_false(range2:is_empty())
|
assert.is_false(range2:is_empty())
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('trim_start', function()
|
it('trim_start', function()
|
||||||
withbuf({ ' line one', 'line two' }, function()
|
withbuf({ ' line one', 'line two' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, 9), 'v')
|
local range = Range.new(Pos.new(nil, 1, 1), Pos.new(nil, 1, 10), 'v')
|
||||||
local trimmed = range:trim_start()
|
local trimmed = range:trim_start()
|
||||||
assert.are.same(trimmed.start, Pos.new(nil, 0, 3)) -- should be after the spaces
|
assert.are.same(trimmed.start, Pos.new(nil, 1, 4)) -- should be after the spaces
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('trim_stop', function()
|
it('trim_stop', function()
|
||||||
withbuf({ 'line one ', 'line two' }, function()
|
withbuf({ 'line one ', 'line two' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, 9), 'v')
|
local range = Range.new(Pos.new(nil, 1, 1), Pos.new(nil, 1, 10), 'v')
|
||||||
local trimmed = range:trim_stop()
|
local trimmed = range:trim_stop()
|
||||||
assert.are.same(trimmed.stop, Pos.new(nil, 0, 7)) -- should be before the spaces
|
assert.are.same(trimmed.stop, Pos.new(nil, 1, 8)) -- should be before the spaces
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('contains', function()
|
it('contains', function()
|
||||||
withbuf({ 'line one', 'and line two' }, function()
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 0, 3), 'v')
|
local range = Range.new(Pos.new(nil, 1, 2), Pos.new(nil, 1, 4), 'v')
|
||||||
local pos = Pos.new(nil, 0, 2)
|
local pos = Pos.new(nil, 1, 3)
|
||||||
assert.is_true(range:contains(pos))
|
assert.is_true(range:contains(pos))
|
||||||
|
|
||||||
pos = Pos.new(nil, 0, 4) -- outside of range
|
pos = Pos.new(nil, 1, 5) -- outside of range
|
||||||
assert.is_false(range:contains(pos))
|
assert.is_false(range:contains(pos))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('shrink', function()
|
it('shrink', function()
|
||||||
withbuf({ 'line one', 'and line two' }, function()
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 1, 3), 'v')
|
local range = Range.new(Pos.new(nil, 2, 3), Pos.new(nil, 3, 5), 'v')
|
||||||
local shrunk = range:shrink(1)
|
local shrunk = range:shrink(1)
|
||||||
assert.are.same(shrunk.start, Pos.new(nil, 0, 2))
|
assert.are.same(shrunk.start, Pos.new(nil, 2, 4))
|
||||||
assert.are.same(shrunk.stop, Pos.new(nil, 1, 2))
|
assert.are.same(shrunk.stop, Pos.new(nil, 3, 4))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('must_shrink', function()
|
it('must_shrink', function()
|
||||||
withbuf({ 'line one', 'and line two' }, function()
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
local range = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 1, 3), 'v')
|
local range = Range.new(Pos.new(nil, 2, 3), Pos.new(nil, 3, 5), 'v')
|
||||||
local shrunk = range:must_shrink(1)
|
local shrunk = range:must_shrink(1)
|
||||||
assert.are.same(shrunk.start, Pos.new(nil, 0, 2))
|
assert.are.same(shrunk.start, Pos.new(nil, 2, 4))
|
||||||
assert.are.same(shrunk.stop, Pos.new(nil, 1, 2))
|
assert.are.same(shrunk.stop, Pos.new(nil, 3, 4))
|
||||||
|
|
||||||
assert.has.error(function() range:must_shrink(100) end, 'error in Range:must_shrink: Range:shrink() returned nil')
|
assert.has.error(function() range:must_shrink(100) end, 'error in Range:must_shrink: Range:shrink() returned nil')
|
||||||
end)
|
end)
|
||||||
@ -451,22 +447,24 @@ describe('Range', function()
|
|||||||
local range = Range.from_lines(nil, 0, 1)
|
local range = Range.from_lines(nil, 0, 1)
|
||||||
range:set_visual_selection()
|
range:set_visual_selection()
|
||||||
|
|
||||||
assert.are.same(Pos.from_pos 'v', Pos.new(nil, 0, 0))
|
assert.are.same(Pos.from_pos 'v', Pos.new(nil, 1, 1))
|
||||||
assert.are.same(Pos.from_pos '.', Pos.new(nil, 1, 11))
|
-- Since the selection is 'V' (instead of 'v'), the end
|
||||||
|
-- selects one character past the end:
|
||||||
|
assert.are.same(Pos.from_pos '.', Pos.new(nil, 2, 13))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('selections set to past the EOL should not error', function()
|
it('selections set to past the EOL should not error', function()
|
||||||
withbuf({ 'Rg SET NAMES' }, function()
|
withbuf({ 'Rg SET NAMES' }, function()
|
||||||
local b = vim.api.nvim_get_current_buf()
|
local b = vim.api.nvim_get_current_buf()
|
||||||
local r = Range.new(Pos.new(b, 0, 3), Pos.new(b, 0, 12), 'v')
|
local r = Range.new(Pos.new(b, 1, 4), Pos.new(b, 1, 13), 'v')
|
||||||
r:replace 'bleh'
|
r:replace 'bleh'
|
||||||
assert.are.same({ 'Rg bleh' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
assert.are.same({ 'Rg bleh' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
withbuf({ 'Rg SET NAMES' }, function()
|
withbuf({ 'Rg SET NAMES' }, function()
|
||||||
local b = vim.api.nvim_get_current_buf()
|
local b = vim.api.nvim_get_current_buf()
|
||||||
local r = Range.new(Pos.new(b, 0, 3), Pos.new(b, 0, 11), 'v')
|
local r = Range.new(Pos.new(b, 1, 4), Pos.new(b, 1, 12), 'v')
|
||||||
r:replace 'bleh'
|
r:replace 'bleh'
|
||||||
assert.are.same({ 'Rg bleh' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
assert.are.same({ 'Rg bleh' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
||||||
end)
|
end)
|
||||||
@ -475,7 +473,7 @@ describe('Range', function()
|
|||||||
it('replace updates Range.stop: same line', function()
|
it('replace updates Range.stop: same line', function()
|
||||||
withbuf({ 'The quick brown fox jumps over the lazy dog' }, function()
|
withbuf({ 'The quick brown fox jumps over the lazy dog' }, function()
|
||||||
local b = vim.api.nvim_get_current_buf()
|
local b = vim.api.nvim_get_current_buf()
|
||||||
local r = Range.new(Pos.new(b, 0, 4), Pos.new(b, 0, 8), 'v')
|
local r = Range.new(Pos.new(b, 1, 5), Pos.new(b, 1, 9), 'v')
|
||||||
|
|
||||||
r:replace 'bleh1'
|
r:replace 'bleh1'
|
||||||
assert.are.same({ 'The bleh1 brown fox jumps over the lazy dog' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
assert.are.same({ 'The bleh1 brown fox jumps over the lazy dog' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
||||||
@ -491,11 +489,12 @@ describe('Range', function()
|
|||||||
'over the lazy dog',
|
'over the lazy dog',
|
||||||
}, function()
|
}, function()
|
||||||
local b = vim.api.nvim_get_current_buf()
|
local b = vim.api.nvim_get_current_buf()
|
||||||
local r = Range.new(Pos.new(b, 0, 20), Pos.new(b, 1, 3), 'v')
|
local r = Range.new(Pos.new(b, 1, 21), Pos.new(b, 2, 4), 'v')
|
||||||
assert.are.same({ 'jumps', 'over' }, r:lines())
|
assert.are.same({ 'jumps', 'over' }, r:lines())
|
||||||
|
|
||||||
r:replace 'bleh1'
|
r:replace 'bleh1'
|
||||||
assert.are.same({ 'The quick brown fox bleh1 the lazy dog' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
assert.are.same({ 'The quick brown fox bleh1 the lazy dog' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
||||||
|
assert.are.same({ 'bleh1' }, r:lines())
|
||||||
|
|
||||||
r:replace 'blehGoo2'
|
r:replace 'blehGoo2'
|
||||||
assert.are.same({ 'The quick brown fox blehGoo2 the lazy dog' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
assert.are.same({ 'The quick brown fox blehGoo2 the lazy dog' }, vim.api.nvim_buf_get_lines(b, 0, -1, false))
|
||||||
@ -511,7 +510,7 @@ describe('Range', function()
|
|||||||
'the lazy dog',
|
'the lazy dog',
|
||||||
}, function()
|
}, function()
|
||||||
local b = vim.api.nvim_get_current_buf()
|
local b = vim.api.nvim_get_current_buf()
|
||||||
local r = Range.new(Pos.new(b, 1, 0), Pos.new(b, 3, Pos.MAX_COL), 'V')
|
local r = Range.new(Pos.new(b, 2, 1), Pos.new(b, 4, Pos.MAX_COL), 'V')
|
||||||
assert.are.same({ 'fox', 'jumps', 'over' }, r:lines())
|
assert.are.same({ 'fox', 'jumps', 'over' }, r:lines())
|
||||||
|
|
||||||
r:replace { 'bleh1', 'bleh2' }
|
r:replace { 'bleh1', 'bleh2' }
|
||||||
@ -540,7 +539,7 @@ describe('Range', function()
|
|||||||
'the lazy dog',
|
'the lazy dog',
|
||||||
}, function()
|
}, function()
|
||||||
local b = vim.api.nvim_get_current_buf()
|
local b = vim.api.nvim_get_current_buf()
|
||||||
local r = Range.new(Pos.new(b, 1, 0), Pos.new(b, 3, Pos.MAX_COL), 'V')
|
local r = Range.new(Pos.new(b, 2, 1), Pos.new(b, 4, Pos.MAX_COL), 'V')
|
||||||
assert.are.same({ 'fox', 'jumps', 'over' }, r:lines())
|
assert.are.same({ 'fox', 'jumps', 'over' }, r:lines())
|
||||||
|
|
||||||
r:replace(nil)
|
r:replace(nil)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user