bugfixes; update README.md
This commit is contained in:
parent
61460f0180
commit
b9edc4d8ff
15
.github/workflows/ci.yaml
vendored
Normal file
15
.github/workflows/ci.yaml
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
||||||
|
name: NeoVim tests
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
plenary-tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
XDG_CONFIG_HOME: ${{ github.workspace }}/.config/
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: rhysd/action-setup-vim@v1
|
||||||
|
with:
|
||||||
|
neovim: true
|
||||||
|
version: v0.10.1
|
||||||
|
- run: make test
|
157
README.md
157
README.md
@ -2,40 +2,123 @@
|
|||||||
|
|
||||||
Welcome to **Text Tools (TT)** – a powerful Lua library designed to enhance your text manipulation experience in NeoVim, focusing primarily on a context-aware "Range" utility. This utility allows you to work efficiently with text selections based on various conditions, in a variety of contexts, making coding and editing more intuitive and productive.
|
Welcome to **Text Tools (TT)** – a powerful Lua library designed to enhance your text manipulation experience in NeoVim, focusing primarily on a context-aware "Range" utility. This utility allows you to work efficiently with text selections based on various conditions, in a variety of contexts, making coding and editing more intuitive and productive.
|
||||||
|
|
||||||
This is meant to be used as a **library**, not a plugin. On its own, text-tools.nvim does nothing on its own. It is meant to be used by plugin authors, to make their lives easier based on the variety of utilities I found I needed while growing my NeoVim config.
|
This is meant to be used as a **library**, not a plugin. On its own, `text-tools.nvim` does nothing on its own. It is meant to be used by plugin authors, to make their lives easier based on the variety of utilities I found I needed while growing my NeoVim config.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Range Utility**: Get context-aware selections with ease.
|
- **Range Utility**: Get context-aware selections with ease. Replace regions with new text. Think of it as a programmatic way to work with visual selections (or regions of text).
|
||||||
- **Code Writer**: Write code with automatic indentation and formatting.
|
- **Code Writer**: Write code with automatic indentation and formatting.
|
||||||
- **Operator Key Mapping**: Flexible key mapping that works with the selected text.
|
- **Operator Key Mapping**: Flexible key mapping that works with the selected text.
|
||||||
- **Text and Position Utilities**: Convenient functions to manage text objects and cursor positions.
|
- **Text and Position Utilities**: Convenient functions to manage text objects and cursor positions.
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
1. Clone the repository:
|
Lazy:
|
||||||
```bash
|
```lua
|
||||||
git clone https://github.com/yourusername/text-tools.git
|
-- Setting `lazy = true` ensures that the library is only loaded
|
||||||
```
|
-- when `require 'tt.<utility>' is called.
|
||||||
2. Add the path to your `init.vim` or `init.lua`:
|
{ 'jrop/text-tools.nvim', lazy = true }
|
||||||
```lua
|
```
|
||||||
package.path = package.path .. ';/path/to/text-tools/lua/?.lua'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
### A note on indices
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
### 1. Creating a Range
|
### 1. Creating a Range
|
||||||
|
|
||||||
To create a range, use the `Range.new(startPos, endPos, mode)` method:
|
The `Range` utility is the main feature upon which most other things in this library are built, aside from a few standalone utilities. Ranges can be constructed manually, or preferably, obtained based on a variety of contexts.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local Range = require 'tt.range'
|
local Range = require 'tt.range'
|
||||||
local startPos = Pos.new(0, 1, 0) -- Line 1, first column
|
local start = Pos.new(0, 0, 0) -- Line 1, first column
|
||||||
local endPos = Pos.new(0, 3, 0) -- Line 3, first column
|
local stop = Pos.new(0, 2, 0) -- Line 3, first column
|
||||||
local myRange = Range.new(startPos, endPos)
|
|
||||||
|
Range.new(start, stop, 'v') -- charwise selection
|
||||||
|
Range.new(start, stop, 'V') -- linewise selection
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Working with Code Writer
|
This is usually not how you want to obtain a `Range`, however. Usually you want to get the corresponding context of an edit operation and just "get me the current Range that represents this context".
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- get the first line in a buffer:
|
||||||
|
Range.from_line(0, 0)
|
||||||
|
|
||||||
|
-- Text Objects (any text object valid in your configuration is supported):
|
||||||
|
-- get the word the cursor is on:
|
||||||
|
Range.from_text_object('iw')
|
||||||
|
-- get the WORD the cursor is on:
|
||||||
|
Range.from_text_object('iW')
|
||||||
|
-- get the "..." the cursor is within:
|
||||||
|
Range.from_text_object('a"')
|
||||||
|
|
||||||
|
-- Get the currently visually selected text:
|
||||||
|
-- NOTE: this does NOT work within certain contexts; more specialized utilities are more appropriate in certain circumstances
|
||||||
|
Range.from_vtext()
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Get the operated on text obtained from a motion:
|
||||||
|
-- (HINT: use the opkeymap utility to make this less verbose)
|
||||||
|
--
|
||||||
|
---@param ty 'char'|'line'|'block'
|
||||||
|
function MyOpFunc(ty)
|
||||||
|
local range = Range.from_op_func(ty)
|
||||||
|
-- do something with the range
|
||||||
|
end
|
||||||
|
-- Try invoking this with: `<Leader>toaw`, and the current word will be the context:
|
||||||
|
vim.keymap.set('<Leader>to', function()
|
||||||
|
vim.g.operatorfunc = 'v:lua.MyOpFunc'
|
||||||
|
return 'g@'
|
||||||
|
end, { expr = true })
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Commands:
|
||||||
|
--
|
||||||
|
-- When executing commands in a visual context, getting the selected text has to be done differently:
|
||||||
|
vim.api.nvim_create_user_command('MyCmd', function(args)
|
||||||
|
local range = Range.from_cmd_args(args)
|
||||||
|
if range == nil then
|
||||||
|
-- the command was executed in normal mode
|
||||||
|
else
|
||||||
|
-- ...
|
||||||
|
end
|
||||||
|
end, { range = true })
|
||||||
|
```
|
||||||
|
|
||||||
|
So far, that's a lot of ways to _get_ a `Range`. But what can you do with a range once you have one? Plenty, it turns out!
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local range = ...
|
||||||
|
range:lines() -- get the lines 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:line0(-1) -- get the last line within this range
|
||||||
|
-- replace with new contents:
|
||||||
|
range:replace {
|
||||||
|
'replacement line 1',
|
||||||
|
'replacement line 2',
|
||||||
|
}
|
||||||
|
range:replace 'with a string'
|
||||||
|
-- delete the contents of the range:
|
||||||
|
range:replace(nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Defining Key Mappings over Motions
|
||||||
|
|
||||||
|
Define custom (dot-repeatable) key mappings for text objects:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local opkeymap = require 'tt.opkeymap'
|
||||||
|
|
||||||
|
-- invoke this function by typing, for example, `<leader>riw`:
|
||||||
|
-- `range` will contain the bounds of the motion `iw`.
|
||||||
|
opkeymap('n', '<leader>r', function(range)
|
||||||
|
print(range:text()) -- Prints the text within the selected range
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Working with Code Writer
|
||||||
|
|
||||||
To write code with indentation, use the `CodeWriter` class:
|
To write code with indentation, use the `CodeWriter` class:
|
||||||
|
|
||||||
@ -44,35 +127,25 @@ local CodeWriter = require 'tt.codewriter'
|
|||||||
local cw = CodeWriter.new()
|
local cw = CodeWriter.new()
|
||||||
cw:write('{')
|
cw:write('{')
|
||||||
cw:indent(function(innerCW)
|
cw:indent(function(innerCW)
|
||||||
innerCW:write('x: 123')
|
innerCW:write('x: 123')
|
||||||
end)
|
end)
|
||||||
cw:write('}')
|
cw:write('}')
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Defining Key Mappings
|
|
||||||
|
|
||||||
Define custom key mappings for text objects:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local opkeymap = require 'tt.opkeymap'
|
|
||||||
|
|
||||||
-- invoke this function by typing, for example, `<leader>riw`:
|
|
||||||
-- `range` will contain the bounds of the motion `iw`.
|
|
||||||
opkeymap('n', '<leader>r', function(range)
|
|
||||||
print(range:text()) -- Prints the text within the selected range
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Utility Functions
|
### 4. Utility Functions
|
||||||
|
|
||||||
#### Cursor Position
|
#### Custom Text Objects
|
||||||
|
|
||||||
To manage cursor position, use the `Pos` class:
|
Simply by returning a `Range` or a `Pos`, you can easily and quickly define your own text objects:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local Pos = require 'tt.pos'
|
local utils = require 'tt.utils'
|
||||||
local cursorPos = Pos.new(0, 1, 5) -- Line 1, character 5
|
local Range = require 'tt.range'
|
||||||
print(cursorPos:char()) -- Gets the character at the cursor position
|
|
||||||
|
-- Select whole file:
|
||||||
|
utils.define_text_object('ag', function()
|
||||||
|
return Range.from_buf_text()
|
||||||
|
end)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Buffer Management
|
#### Buffer Management
|
||||||
@ -82,7 +155,19 @@ Access and manipulate buffers easily:
|
|||||||
```lua
|
```lua
|
||||||
local Buffer = require 'tt.buffer'
|
local Buffer = require 'tt.buffer'
|
||||||
local buf = Buffer.current()
|
local buf = Buffer.current()
|
||||||
print(buf:line_count()) -- Number of lines in the current buffer
|
buf:line_count() -- the number of lines in the current buffer
|
||||||
|
buf:get_option '...'
|
||||||
|
buf:set_option('...', ...)
|
||||||
|
buf:get_var '...'
|
||||||
|
buf:set_var('...', ...)
|
||||||
|
buf:all() -- returns a Range representing the entire buffer
|
||||||
|
buf:is_empty() -- returns true if the buffer has no text
|
||||||
|
buf:append_line '...'
|
||||||
|
buf:line0(0) -- returns a Range representing the first line in the buffer
|
||||||
|
buf:line0(-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 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
|
||||||
```
|
```
|
||||||
|
|
||||||
## License (MIT)
|
## License (MIT)
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
local function withbuf(lines, f)
|
local function withbuf(lines, f)
|
||||||
|
vim.opt_global.swapfile = false
|
||||||
|
|
||||||
vim.cmd.new()
|
vim.cmd.new()
|
||||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
|
||||||
local ok, result = pcall(f)
|
local ok, result = pcall(f)
|
||||||
|
@ -6,7 +6,7 @@ local Buffer = {}
|
|||||||
|
|
||||||
---@param buf? number
|
---@param buf? number
|
||||||
---@return Buffer
|
---@return Buffer
|
||||||
function Buffer.new(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
|
||||||
local b = { buf = buf }
|
local b = { buf = buf }
|
||||||
setmetatable(b, { __index = Buffer })
|
setmetatable(b, { __index = Buffer })
|
||||||
@ -14,12 +14,12 @@ function Buffer.new(buf)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@return Buffer
|
---@return Buffer
|
||||||
function Buffer.current() return Buffer.new(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 Buffer
|
||||||
function Buffer.create(listed, scratch) return Buffer.new(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()
|
||||||
self:set_option('bufhidden', 'delete')
|
self:set_option('bufhidden', 'delete')
|
||||||
|
@ -24,7 +24,7 @@ end
|
|||||||
|
|
||||||
---@param p Pos
|
---@param p Pos
|
||||||
function CodeWriter.from_pos(p)
|
function CodeWriter.from_pos(p)
|
||||||
local line = Buffer.new(p.buf):line0(p.lnum):text()
|
local line = Buffer.from_nr(p.buf):line0(p.lnum):text()
|
||||||
return CodeWriter.from_line(line, p.buf)
|
return CodeWriter.from_line(line, p.buf)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -55,8 +55,6 @@ end
|
|||||||
---@param line string
|
---@param line string
|
||||||
function CodeWriter:write_raw(line)
|
function CodeWriter:write_raw(line)
|
||||||
if line:find '\n' then error 'line contains newline character' end
|
if line:find '\n' then error 'line contains newline character' end
|
||||||
line = line:gsub('^\n+', '')
|
|
||||||
line = line:gsub('\n+$', '')
|
|
||||||
table.insert(self.lines, line)
|
table.insert(self.lines, line)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2,20 +2,20 @@ local Range = require 'tt.range'
|
|||||||
local vim_repeat = require 'tt.repeat'
|
local vim_repeat = require 'tt.repeat'
|
||||||
|
|
||||||
---@type fun(range: Range): nil|(fun():any)
|
---@type fun(range: Range): nil|(fun():any)
|
||||||
local MyOpKeymapOpFunc_rhs = nil
|
local __TT__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: Range): fun():any|nil
|
||||||
---@param ty 'line'|'char'|'block'
|
---@param ty 'line'|'char'|'block'
|
||||||
-- selene: allow(unused_variable)
|
-- selene: allow(unused_variable)
|
||||||
function MyOpKeymapOpFunc(ty)
|
function __TT__OpKeymapOpFunc(ty)
|
||||||
if MyOpKeymapOpFunc_rhs ~= nil then
|
if __TT__OpKeymapOpFunc_rhs ~= nil then
|
||||||
local range = Range.from_op_func(ty)
|
local range = Range.from_op_func(ty)
|
||||||
local repeat_inject = MyOpKeymapOpFunc_rhs(range)
|
local repeat_inject = __TT__OpKeymapOpFunc_rhs(range)
|
||||||
|
|
||||||
vim_repeat.set(function()
|
vim_repeat.set(function()
|
||||||
vim.o.operatorfunc = 'v:lua.MyOpKeymapOpFunc'
|
vim.o.operatorfunc = 'v:lua.__TT__OpKeymapOpFunc'
|
||||||
if repeat_inject ~= nil and type(repeat_inject) == 'function' then repeat_inject() end
|
if repeat_inject ~= nil and type(repeat_inject) == 'function' then repeat_inject() end
|
||||||
vim_repeat.native_repeat()
|
vim_repeat.native_repeat()
|
||||||
end)
|
end)
|
||||||
@ -34,8 +34,8 @@ end
|
|||||||
---@param opts? vim.keymap.set.Opts
|
---@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()
|
||||||
MyOpKeymapOpFunc_rhs = rhs
|
__TT__OpKeymapOpFunc_rhs = rhs
|
||||||
vim.o.operatorfunc = 'v:lua.MyOpKeymapOpFunc'
|
vim.o.operatorfunc = 'v:lua.__TT__OpKeymapOpFunc'
|
||||||
return 'g@'
|
return 'g@'
|
||||||
end, vim.tbl_extend('force', opts or {}, { expr = true }))
|
end, vim.tbl_extend('force', opts or {}, { expr = true }))
|
||||||
end
|
end
|
||||||
|
@ -78,6 +78,10 @@ function Range.from_line(buf, line) return Range.from_lines(buf, line, line) end
|
|||||||
---@param stop_line number 0-based line index
|
---@param stop_line number 0-based line index
|
||||||
function Range.from_lines(buf, start_line, stop_line)
|
function Range.from_lines(buf, start_line, stop_line)
|
||||||
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 stop_line < 0 then
|
||||||
|
local num_lines = vim.api.nvim_buf_line_count(buf)
|
||||||
|
stop_line = num_lines + stop_line
|
||||||
|
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, 0), Pos.new(buf, stop_line, Pos.MAX_COL), 'V')
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -237,7 +241,7 @@ function Range.smallest(ranges)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Range:clone() return Range.new(self.start:clone(), self.stop:clone(), self.mode) end
|
function Range:clone() return Range.new(self.start:clone(), self.stop:clone(), self.mode) end
|
||||||
function Range:line_count() return self.stop.lnum + self.start.lnum + 1 end
|
function Range:line_count() return self.stop.lnum - self.start.lnum + 1 end
|
||||||
|
|
||||||
function Range:to_linewise()
|
function Range:to_linewise()
|
||||||
local r = self:clone()
|
local r = self:clone()
|
||||||
@ -369,7 +373,7 @@ end
|
|||||||
---@param amount number
|
---@param amount number
|
||||||
function Range:must_shrink(amount)
|
function Range:must_shrink(amount)
|
||||||
local shrunk = self:shrink(amount)
|
local shrunk = self:shrink(amount)
|
||||||
if shrunk == nil then error 'error in Range:must_shrink: Range:shrink() returned nil' end
|
if shrunk == nil or shrunk:is_empty() then error 'error in Range:must_shrink: Range:shrink() returned nil' end
|
||||||
return shrunk
|
return shrunk
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -397,12 +401,8 @@ function Range:set_visual_selection()
|
|||||||
self.start:save_to_mark 'a'
|
self.start:save_to_mark 'a'
|
||||||
self.stop:save_to_mark 'b'
|
self.stop:save_to_mark 'b'
|
||||||
local mode = self.mode
|
local mode = self.mode
|
||||||
if vim.api.nvim_get_mode().mode == 'n' then
|
if vim.api.nvim_get_mode().mode == 'n' then utils.feedkeys(mode) end
|
||||||
vim.cmd.normal { cmd = 'normal', args = { '`a' .. mode .. '`b' }, bang = true }
|
utils.feedkeys '`ao`b'
|
||||||
else
|
|
||||||
utils.feedkeys '`ao`b'
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -4,10 +4,27 @@ local withbuf = require '__tt_test_tools'
|
|||||||
describe('Buffer', function()
|
describe('Buffer', function()
|
||||||
it('should replace all lines', function()
|
it('should replace all lines', function()
|
||||||
withbuf({}, function()
|
withbuf({}, function()
|
||||||
local buf = Buffer.new()
|
local buf = Buffer.from_nr()
|
||||||
buf:all():replace 'bleh'
|
buf:all():replace 'bleh'
|
||||||
local actual_lines = vim.api.nvim_buf_get_lines(buf.buf, 0, -1, false)
|
local actual_lines = vim.api.nvim_buf_get_lines(buf.buf, 0, -1, false)
|
||||||
assert.are.same({ 'bleh' }, actual_lines)
|
assert.are.same({ 'bleh' }, actual_lines)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('should replace all but first and last lines', function()
|
||||||
|
withbuf({
|
||||||
|
'one',
|
||||||
|
'two',
|
||||||
|
'three',
|
||||||
|
}, function()
|
||||||
|
local buf = Buffer.from_nr()
|
||||||
|
buf:lines(1, -2):replace 'too'
|
||||||
|
local actual_lines = vim.api.nvim_buf_get_lines(buf.buf, 0, -1, false)
|
||||||
|
assert.are.same({
|
||||||
|
'one',
|
||||||
|
'too',
|
||||||
|
'three',
|
||||||
|
}, actual_lines)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
@ -276,4 +276,183 @@ describe('Range', function()
|
|||||||
assert.are.same('block', range:line0(1).text())
|
assert.are.same('block', range:line0(1).text())
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('from_marks', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local a = Pos.new(nil, 0, 0)
|
||||||
|
local b = Pos.new(nil, 1, 1)
|
||||||
|
a:save_to_pos "'["
|
||||||
|
b:save_to_pos "']"
|
||||||
|
|
||||||
|
local range = Range.from_marks("'[", "']")
|
||||||
|
assert.are.same(range.start, a)
|
||||||
|
assert.are.same(range.stop, b)
|
||||||
|
assert.are.same(range.mode, 'v')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('from_vtext', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
vim.fn.setpos('.', { 0, 1, 3, 0 }) -- cursor at position (1, 3)
|
||||||
|
vim.cmd.normal 'v' -- enter visual mode
|
||||||
|
vim.cmd.normal 'l' -- select one character to the right
|
||||||
|
local range = Range.from_vtext()
|
||||||
|
assert.are.same(range.start, Pos.new(nil, 0, 2))
|
||||||
|
assert.are.same(range.stop, Pos.new(nil, 0, 3))
|
||||||
|
assert.are.same(range.mode, 'v')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('from_op_func', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local a = Pos.new(nil, 0, 0)
|
||||||
|
local b = Pos.new(nil, 1, 1)
|
||||||
|
a:save_to_pos "'["
|
||||||
|
b:save_to_pos "']"
|
||||||
|
|
||||||
|
local range = Range.from_op_func 'char'
|
||||||
|
assert.are.same(range.start, a)
|
||||||
|
assert.are.same(range.stop, b)
|
||||||
|
assert.are.same(range.mode, 'v')
|
||||||
|
|
||||||
|
range = Range.from_op_func 'line'
|
||||||
|
assert.are.same(range.start, a)
|
||||||
|
assert.are.same(range.stop, Pos.new(nil, 1, Pos.MAX_COL))
|
||||||
|
assert.are.same(range.mode, 'V')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('from_cmd_args', function()
|
||||||
|
local args = { range = 1 }
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local a = Pos.new(nil, 0, 0)
|
||||||
|
local b = Pos.new(nil, 1, 1)
|
||||||
|
a:save_to_pos "'<"
|
||||||
|
b:save_to_pos "'>"
|
||||||
|
|
||||||
|
local range = Range.from_cmd_args(args)
|
||||||
|
assert.are.same(range.start, a)
|
||||||
|
assert.are.same(range.stop, b)
|
||||||
|
assert.are.same(range.mode, 'v')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('find_nearest_quotes', function()
|
||||||
|
withbuf({ [[the "quick" brown fox]] }, function()
|
||||||
|
vim.fn.setpos('.', { 0, 1, 5, 0 })
|
||||||
|
local range = Range.find_nearest_quotes()
|
||||||
|
assert.are.same(range.start, Pos.new(nil, 0, 4))
|
||||||
|
assert.are.same(range.stop, Pos.new(nil, 0, 10))
|
||||||
|
end)
|
||||||
|
|
||||||
|
withbuf({ [[the 'quick' brown fox]] }, function()
|
||||||
|
vim.fn.setpos('.', { 0, 1, 5, 0 })
|
||||||
|
local range = Range.find_nearest_quotes()
|
||||||
|
assert.are.same(range.start, Pos.new(nil, 0, 4))
|
||||||
|
assert.are.same(range.stop, Pos.new(nil, 0, 10))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('smallest', function()
|
||||||
|
local r1 = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 0, 3), 'v')
|
||||||
|
local r2 = Range.new(Pos.new(nil, 0, 2), Pos.new(nil, 0, 4), 'v')
|
||||||
|
local r3 = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, 5), 'v')
|
||||||
|
local smallest = Range.smallest { r1, r2, r3 }
|
||||||
|
assert.are.same(smallest.start, Pos.new(nil, 0, 1))
|
||||||
|
assert.are.same(smallest.stop, Pos.new(nil, 0, 3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('clone', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local original = Range.from_lines(nil, 0, 1)
|
||||||
|
local cloned = original:clone()
|
||||||
|
assert.are.same(original.start, cloned.start)
|
||||||
|
assert.are.same(original.stop, cloned.stop)
|
||||||
|
assert.are.same(original.mode, cloned.mode)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('line_count', function()
|
||||||
|
withbuf({ 'line one', 'and line two', 'line three' }, function()
|
||||||
|
local range = Range.from_lines(nil, 0, 2)
|
||||||
|
assert.are.same(range:line_count(), 3)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('to_linewise()', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local range = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 1, 3), 'v')
|
||||||
|
local linewise_range = range:to_linewise()
|
||||||
|
assert.are.same(linewise_range.start.col, 0)
|
||||||
|
assert.are.same(linewise_range.stop.col, Pos.MAX_COL)
|
||||||
|
assert.are.same(linewise_range.mode, 'V')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('is_empty', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local range = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, 0), 'v')
|
||||||
|
assert.is_true(range:is_empty())
|
||||||
|
|
||||||
|
local range2 = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, 1), 'v')
|
||||||
|
assert.is_false(range2:is_empty())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('trim_start', function()
|
||||||
|
withbuf({ ' line one', 'line two' }, function()
|
||||||
|
local range = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, 9), 'v')
|
||||||
|
local trimmed = range:trim_start()
|
||||||
|
assert.are.same(trimmed.start, Pos.new(nil, 0, 3)) -- should be after the spaces
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('trim_stop', function()
|
||||||
|
withbuf({ 'line one ', 'line two' }, function()
|
||||||
|
local range = Range.new(Pos.new(nil, 0, 0), Pos.new(nil, 0, 9), 'v')
|
||||||
|
local trimmed = range:trim_stop()
|
||||||
|
assert.are.same(trimmed.stop, Pos.new(nil, 0, 7)) -- should be before the spaces
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('contains', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local range = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 0, 3), 'v')
|
||||||
|
local pos = Pos.new(nil, 0, 2)
|
||||||
|
assert.is_true(range:contains(pos))
|
||||||
|
|
||||||
|
pos = Pos.new(nil, 0, 4) -- outside of range
|
||||||
|
assert.is_false(range:contains(pos))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('shrink', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local range = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 1, 3), 'v')
|
||||||
|
local shrunk = range:shrink(1)
|
||||||
|
assert.are.same(shrunk.start, Pos.new(nil, 0, 2))
|
||||||
|
assert.are.same(shrunk.stop, Pos.new(nil, 1, 2))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('must_shrink', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local range = Range.new(Pos.new(nil, 0, 1), Pos.new(nil, 1, 3), 'v')
|
||||||
|
local shrunk = range:must_shrink(1)
|
||||||
|
assert.are.same(shrunk.start, Pos.new(nil, 0, 2))
|
||||||
|
assert.are.same(shrunk.stop, Pos.new(nil, 1, 2))
|
||||||
|
|
||||||
|
assert.has.error(function() range:must_shrink(100) end, 'error in Range:must_shrink: Range:shrink() returned nil')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('set_visual_selection', function()
|
||||||
|
withbuf({ 'line one', 'and line two' }, function()
|
||||||
|
local range = Range.from_lines(nil, 0, 1)
|
||||||
|
range:set_visual_selection()
|
||||||
|
|
||||||
|
assert.are.same(Pos.from_pos 'v', Pos.new(nil, 0, 0))
|
||||||
|
assert.are.same(Pos.from_pos '.', Pos.new(nil, 1, 11))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
12
vim.yml
12
vim.yml
@ -11,6 +11,18 @@ globals:
|
|||||||
args:
|
args:
|
||||||
- type: any
|
- type: any
|
||||||
- type: any
|
- type: any
|
||||||
|
assert.has.error:
|
||||||
|
args:
|
||||||
|
- type: any
|
||||||
|
- type: any
|
||||||
|
assert.is_true:
|
||||||
|
args:
|
||||||
|
- type: any
|
||||||
|
- type: any
|
||||||
|
assert.is_false:
|
||||||
|
args:
|
||||||
|
- type: any
|
||||||
|
- type: any
|
||||||
describe:
|
describe:
|
||||||
args:
|
args:
|
||||||
- type: string
|
- type: string
|
||||||
|
Loading…
x
Reference in New Issue
Block a user