diff --git a/lua/u/range.lua b/lua/u/range.lua index 5a96b51..a40312c 100644 --- a/lua/u/range.lua +++ b/lua/u/range.lua @@ -249,19 +249,22 @@ end --- @param args unknown --- @return u.Range|nil function Range.from_cmd_args(args) - --- @type 'v'|'V' - local mode - --- @type nil|u.Pos - local start - local stop - if args.range == 0 then - return nil - else - start = Pos.from_pos "'<" - stop = Pos.from_pos "'>" - mode = stop:is_col_max() and 'V' or 'v' + if args.range == 0 then return nil end + + local bufnr = vim.api.nvim_get_current_buf() + if args.range == 1 then + return Range.new(Pos.new(bufnr, args.line1, 1), Pos.new(bufnr, args.line1, Pos.MAX_COL), 'V') + end + + local is_visual = vim.fn.histget('cmd', -1):sub(1, 5) == [['<,'>]] + --- @type 'v'|'V' + local mode = is_visual and vim.fn.visualmode() or 'V' + + if is_visual then + return Range.new(Pos.from_pos "'<", Pos.from_pos "'>", mode) + else + return Range.new(Pos.new(bufnr, args.line1, 1), Pos.new(bufnr, args.line2, Pos.MAX_COL), mode) end - return Range.new(start, stop, mode) end function Range.find_nearest_brackets() diff --git a/lua/u/renderer.lua b/lua/u/renderer.lua index 625dbf9..8aebf77 100644 --- a/lua/u/renderer.lua +++ b/lua/u/renderer.lua @@ -526,7 +526,8 @@ function TreeBuilder:nest(fn) return self end ---- @param arr [] +--- @generic T +--- @param arr T[] --- @param f fun(tb: u.renderer.TreeBuilder, item: T, idx: number): any function TreeBuilder:ipairs(arr, f) return self:nest(function(tb) diff --git a/spec/range_spec.lua b/spec/range_spec.lua index 0a7f46a..e60f8a1 100644 --- a/spec/range_spec.lua +++ b/spec/range_spec.lua @@ -318,18 +318,89 @@ describe('Range', function() end) end) - it('from_cmd_args', function() - local args = { range = 1 } + it('from_cmd_args: range=0', function() + local args = { range = 0 } + withbuf( + { 'line one', 'and line two' }, + function() assert.are.same(Range.from_cmd_args(args), nil) end + ) + end) + + it('from_cmd_args: range=1', function() + local args = { range = 1, line1 = 1 } withbuf({ 'line one', 'and line two' }, function() local a = Pos.new(nil, 1, 1) - local b = Pos.new(nil, 2, 2) + local b = Pos.new(nil, 1, Pos.MAX_COL) + + local range = Range.from_cmd_args(args) --[[@as u.Range]] + assert.are.same(range.start, a) + assert.are.same(range.stop, b) + assert.are.same(range.mode, 'V') + assert.are.same(range:text(), 'line one') + end) + end) + + it('from_cmd_args: range=2: no-visual', function() + local args = { range = 2, line1 = 1, line2 = 2 } + withbuf({ 'line one', 'and line two' }, function() + local range = Range.from_cmd_args(args) --[[@as u.Range]] + assert.are.same(range.start, Pos.new(nil, 1, 1)) + assert.are.same(range.stop, Pos.new(nil, 2, Pos.MAX_COL)) + assert.are.same(range.mode, 'V') + assert.are.same(range:text(), 'line one\nand line two') + end) + end) + + it('from_cmd_args: range=2: visual: linewise', function() + local args = { range = 2, line1 = 1, line2 = 2 } + withbuf({ 'line one', 'and line two' }, function() + local a = Pos.new(nil, 1, 1) + local b = Pos.new(nil, 2, Pos.MAX_COL) a:save_to_pos "'<" b:save_to_pos "'>" + local range = Range.from_cmd_args(args) --[[@as u.Range]] + assert.are.same(range.start, a) + assert.are.same(range.stop, b) + assert.are.same(range.mode, 'V') + assert.are.same(range:text(), 'line one\nand line two') + end) + end) - local range = Range.from_cmd_args(args) + it('from_cmd_args: range=2: visual: charwise', function() + local args = { range = 2, line1 = 1, line2 = 1 } + withbuf({ 'line one', 'and line two' }, function() + -- Simulate a visual selection: + local a = Pos.new(nil, 1, 1) + local b = Pos.new(nil, 1, 4) + a:save_to_pos "'<" + b:save_to_pos "'>" + Range.new(a, b, 'v'):set_visual_selection() + assert.are.same(vim.fn.mode(), 'v') + + -- In this simulated setup, we need to force visualmode() to return + -- 'v' and histget() to return [['<,'>]]: + + -- visualmode() + local orig_visualmode = vim.fn.visualmode + --- @diagnostic disable-next-line: duplicate-set-field + function vim.fn.visualmode() return 'v' end + assert.are.same(vim.fn.visualmode(), 'v') + + -- histget() + local orig_histget = vim.fn.histget + --- @diagnostic disable-next-line: duplicate-set-field + function vim.fn.histget(x, y) return [['<,'>]] end + + -- Now run the test: + local range = Range.from_cmd_args(args) --[[@as u.Range]] assert.are.same(range.start, a) assert.are.same(range.stop, b) assert.are.same(range.mode, 'v') + assert.are.same(range:text(), 'line') + + -- Reset visualmode() and histget(): + vim.fn.visualmode = orig_visualmode + vim.fn.histget = orig_histget end) end)