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. | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| - **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. | ||||
| - **Operator Key Mapping**: Flexible key mapping that works with the selected text. | ||||
| - **Text and Position Utilities**: Convenient functions to manage text objects and cursor positions. | ||||
| 
 | ||||
| ### Installation | ||||
| 
 | ||||
| 1. Clone the repository: | ||||
|    ```bash | ||||
|    git clone https://github.com/yourusername/text-tools.git | ||||
|    ``` | ||||
| 2. Add the path to your `init.vim` or `init.lua`: | ||||
|    ```lua | ||||
|    package.path = package.path .. ';/path/to/text-tools/lua/?.lua' | ||||
|    ``` | ||||
| Lazy: | ||||
| ```lua | ||||
| -- Setting `lazy = true` ensures that the library is only loaded | ||||
| -- when `require 'tt.<utility>' is called. | ||||
| { 'jrop/text-tools.nvim', lazy = true } | ||||
| ``` | ||||
| 
 | ||||
| ## 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 | ||||
| 
 | ||||
| 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 | ||||
| local Range = require 'tt.range' | ||||
| local startPos = Pos.new(0, 1, 0)  -- Line 1, first column | ||||
| local endPos = Pos.new(0, 3, 0)    -- Line 3, first column | ||||
| local myRange = Range.new(startPos, endPos) | ||||
| local start = Pos.new(0, 0, 0) -- Line 1, first column | ||||
| local stop = Pos.new(0, 2, 0) -- Line 3, first column | ||||
| 
 | ||||
| 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: | ||||
| 
 | ||||
| @ -44,35 +127,25 @@ local CodeWriter = require 'tt.codewriter' | ||||
| local cw = CodeWriter.new() | ||||
| cw:write('{') | ||||
| cw:indent(function(innerCW) | ||||
|     innerCW:write('x: 123') | ||||
|   innerCW:write('x: 123') | ||||
| end) | ||||
| 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 | ||||
| 
 | ||||
| #### 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 | ||||
| local Pos = require 'tt.pos' | ||||
| local cursorPos = Pos.new(0, 1, 5)  -- Line 1, character 5 | ||||
| print(cursorPos:char())  -- Gets the character at the cursor position | ||||
| local utils = require 'tt.utils' | ||||
| local Range = require 'tt.range' | ||||
| 
 | ||||
| -- Select whole file: | ||||
| utils.define_text_object('ag', function() | ||||
|   return Range.from_buf_text() | ||||
| end) | ||||
| ``` | ||||
| 
 | ||||
| #### Buffer Management | ||||
| @ -82,7 +155,19 @@ Access and manipulate buffers easily: | ||||
| ```lua | ||||
| local Buffer = require 'tt.buffer' | ||||
| 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) | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| local function withbuf(lines, f) | ||||
|   vim.opt_global.swapfile = false | ||||
| 
 | ||||
|   vim.cmd.new() | ||||
|   vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) | ||||
|   local ok, result = pcall(f) | ||||
|  | ||||
| @ -6,7 +6,7 @@ local Buffer = {} | ||||
| 
 | ||||
| ---@param buf? number | ||||
| ---@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 | ||||
|   local b = { buf = buf } | ||||
|   setmetatable(b, { __index = Buffer }) | ||||
| @ -14,12 +14,12 @@ function Buffer.new(buf) | ||||
| end | ||||
| 
 | ||||
| ---@return Buffer | ||||
| function Buffer.current() return Buffer.new(0) end | ||||
| function Buffer.current() return Buffer.from_nr(0) end | ||||
| 
 | ||||
| ---@param listed boolean | ||||
| ---@param scratch boolean | ||||
| ---@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() | ||||
|   self:set_option('bufhidden', 'delete') | ||||
|  | ||||
| @ -24,7 +24,7 @@ end | ||||
| 
 | ||||
| ---@param p Pos | ||||
| 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) | ||||
| end | ||||
| 
 | ||||
| @ -55,8 +55,6 @@ end | ||||
| ---@param line string | ||||
| function CodeWriter:write_raw(line) | ||||
|   if line:find '\n' then error 'line contains newline character' end | ||||
|   line = line:gsub('^\n+', '') | ||||
|   line = line:gsub('\n+$', '') | ||||
|   table.insert(self.lines, line) | ||||
| end | ||||
| 
 | ||||
|  | ||||
| @ -2,20 +2,20 @@ local Range = require 'tt.range' | ||||
| local vim_repeat = require 'tt.repeat' | ||||
| 
 | ||||
| ---@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 | ||||
| --- in opkeymap | ||||
| ---@type nil|fun(range: Range): fun():any|nil | ||||
| ---@param ty 'line'|'char'|'block' | ||||
| -- selene: allow(unused_variable) | ||||
| function MyOpKeymapOpFunc(ty) | ||||
|   if MyOpKeymapOpFunc_rhs ~= nil then | ||||
| function __TT__OpKeymapOpFunc(ty) | ||||
|   if __TT__OpKeymapOpFunc_rhs ~= nil then | ||||
|     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.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 | ||||
|       vim_repeat.native_repeat() | ||||
|     end) | ||||
| @ -34,8 +34,8 @@ end | ||||
| ---@param opts? vim.keymap.set.Opts | ||||
| local function opkeymap(mode, lhs, rhs, opts) | ||||
|   vim.keymap.set(mode, lhs, function() | ||||
|     MyOpKeymapOpFunc_rhs = rhs | ||||
|     vim.o.operatorfunc = 'v:lua.MyOpKeymapOpFunc' | ||||
|     __TT__OpKeymapOpFunc_rhs = rhs | ||||
|     vim.o.operatorfunc = 'v:lua.__TT__OpKeymapOpFunc' | ||||
|     return 'g@' | ||||
|   end, vim.tbl_extend('force', opts or {}, { expr = true })) | ||||
| 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 | ||||
| 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 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') | ||||
| end | ||||
| 
 | ||||
| @ -237,7 +241,7 @@ function Range.smallest(ranges) | ||||
| 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() | ||||
|   local r = self:clone() | ||||
| @ -369,7 +373,7 @@ end | ||||
| ---@param amount number | ||||
| function Range:must_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 | ||||
| end | ||||
| 
 | ||||
| @ -397,12 +401,8 @@ function Range:set_visual_selection() | ||||
|     self.start:save_to_mark 'a' | ||||
|     self.stop:save_to_mark 'b' | ||||
|     local mode = self.mode | ||||
|     if vim.api.nvim_get_mode().mode == 'n' then | ||||
|       vim.cmd.normal { cmd = 'normal', args = { '`a' .. mode .. '`b' }, bang = true } | ||||
|     else | ||||
|       utils.feedkeys '`ao`b' | ||||
|     end | ||||
| 
 | ||||
|     if vim.api.nvim_get_mode().mode == 'n' then utils.feedkeys(mode) end | ||||
|     utils.feedkeys '`ao`b' | ||||
|     return nil | ||||
|   end) | ||||
| end | ||||
|  | ||||
| @ -4,10 +4,27 @@ local withbuf = require '__tt_test_tools' | ||||
| describe('Buffer', function() | ||||
|   it('should replace all lines', function() | ||||
|     withbuf({}, function() | ||||
|       local buf = Buffer.new() | ||||
|       local buf = Buffer.from_nr() | ||||
|       buf:all():replace 'bleh' | ||||
|       local actual_lines = vim.api.nvim_buf_get_lines(buf.buf, 0, -1, false) | ||||
|       assert.are.same({ 'bleh' }, actual_lines) | ||||
|     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) | ||||
|  | ||||
| @ -276,4 +276,183 @@ describe('Range', function() | ||||
|       assert.are.same('block', range:line0(1).text()) | ||||
|     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) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user