diff --git a/.gitignore b/.gitignore index 8d65251..b5683a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /.lux/ +lux.lock *.src.rock diff --git a/lua/u/buffer.lua b/lua/u/buffer.lua index 9a2c2c0..238249f 100644 --- a/lua/u/buffer.lua +++ b/lua/u/buffer.lua @@ -67,11 +67,11 @@ end --- @param stop number 1-based line index function Buffer:lines(start, stop) return Range.from_lines(self.bufnr, start, stop) end ---- @param txt_obj string +--- @param motion string --- @param opts? { contains_cursor?: boolean; pos?: u.Pos } -function Buffer:txtobj(txt_obj, opts) +function Buffer:motion(motion, opts) opts = vim.tbl_extend('force', opts or {}, { bufnr = self.bufnr }) - return Range.from_motion(txt_obj, opts) + return Range.from_motion(motion, opts) end --- @param event string|string[] diff --git a/lua/u/pos.lua b/lua/u/pos.lua index bea727e..adc0c89 100644 --- a/lua/u/pos.lua +++ b/lua/u/pos.lua @@ -77,7 +77,7 @@ function Pos.from_pos(name) return Pos.new(p[1], p[2], p[3], p[4]) end -function Pos:is_invalid() return self.bufnr == 0 and self.lnum == 0 and self.col == 0 and self.off == 0 end +function Pos:is_invalid() return self.lnum == 0 and self.col == 0 and self.off == 0 end function Pos:clone() return Pos.new(self.bufnr, self.lnum, self.col, self.off) end diff --git a/lua/u/range.lua b/lua/u/range.lua index 99f54a4..03d195b 100644 --- a/lua/u/range.lua +++ b/lua/u/range.lua @@ -92,35 +92,35 @@ function Range.from_lines(bufnr, start_line, stop_line) return Range.new(Pos.new(bufnr, start_line, 1), Pos.new(bufnr, stop_line, Pos.MAX_COL), 'V') end ---- @param text_obj string +--- @param motion string --- @param opts? { bufnr?: number; contains_cursor?: boolean; pos?: u.Pos, user_defined?: boolean } --- @return u.Range|nil -function Range.from_motion(text_obj, opts) +function Range.from_motion(motion, opts) + -- Options handling: opts = opts or {} if opts.bufnr == nil then opts.bufnr = vim.api.nvim_get_current_buf() end if opts.contains_cursor == nil then opts.contains_cursor = false end if opts.user_defined == nil then opts.user_defined = false end - --- @type "a" | "i" - local selection_type = text_obj:sub(1, 1) - local obj_type = text_obj:sub(#text_obj, #text_obj) - local is_quote = vim.tbl_contains({ "'", '"', '`' }, obj_type) - local cursor = Pos.from_pos '.' + -- Extract some information from the motion: + --- @type 'a'|'i', string + local scope, motion_rest = motion:sub(1, 1), motion:sub(2) + local is_txtobj = scope == 'a' or scope == 'i' + local is_quote_txtobj = is_txtobj and vim.tbl_contains({ "'", '"', '`' }, motion_rest) --- @type u.Pos local start --- @type u.Pos local stop - + -- Capture the original state of the buffer for restoration later. + local original_state = { + winview = vim.fn.winsaveview(), + regquote = vim.fn.getreg '"', + cursor = vim.fn.getpos '.', + pos_lbrack = vim.fn.getpos "'[", + pos_rbrack = vim.fn.getpos "']", + } vim.api.nvim_buf_call(opts.bufnr, function() - local original_state = { - winview = vim.fn.winsaveview(), - regquote = vim.fn.getreg '"', - posdot = vim.fn.getpos '.', - poslb = vim.fn.getpos "'[", - posrb = vim.fn.getpos "']", - } - if opts.pos ~= nil then opts.pos:save_to_pos '.' end Pos.invalid():save_to_pos "'[" @@ -131,39 +131,41 @@ function Range.from_motion(text_obj, opts) vim.cmd { cmd = 'normal', bang = not opts.user_defined, - args = { '""y' .. text_obj }, + args = { '""y' .. motion }, mods = { silent = true }, } on_yank_enabled = prev_on_yank_enabled start = Pos.from_pos "'[" stop = Pos.from_pos "']" - - -- Restore original state: - vim.fn.winrestview(original_state.winview) - vim.fn.setreg('"', original_state.regquote) - vim.fn.setpos('.', original_state.posdot) - vim.fn.setpos("'[", original_state.poslb) - vim.fn.setpos("']", original_state.posrb) - - if - -- I have no idea why, but when yanking `i"`, the stop-mark is - -- placed on the ending quote. For other text-objects, the stop- - -- mark is placed before the closing character. - (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) + -- Restore original state: + vim.fn.winrestview(original_state.winview) + vim.fn.setreg('"', original_state.regquote) + vim.fn.setpos('.', original_state.cursor) + vim.fn.setpos("'[", original_state.pos_lbrack) + vim.fn.setpos("']", original_state.pos_rbrack) if start == stop and start:is_invalid() 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 - start = start:find_next(1, obj_type) or start - stop = stop:find_next(-1, obj_type) or stop + -- Fixup the bounds: + if + -- I have no idea why, but when yanking `i"`, the stop-mark is + -- placed on the ending quote. For other text-objects, the stop- + -- mark is placed before the closing character. + (is_quote_txtobj and scope == 'i' and stop:char() == motion_rest) + -- *Sigh*, this also sometimes happens for `it` as well. + or (motion == 'it' and stop:char() == '<') + then + stop = stop:next(-1) or stop + end + if is_quote_txtobj and scope == 'a' then + start = start:find_next(1, motion_rest) or start + stop = stop:find_next(-1, motion_rest) or stop + end + + if opts.contains_cursor and not Range.new(start, stop):contains(Pos.new(unpack(original_state.cursor))) then + return nil end return Range.new(start, stop) diff --git a/lux.toml b/lux.toml index 45d42c6..394f8e2 100644 --- a/lux.toml +++ b/lux.toml @@ -1,5 +1,5 @@ package = "u.nvim" -version = "0.1.0" +version = "0.2.0" lua = ">=5.1" [description]