diff --git a/lua/argwrap/init.lua b/lua/argwrap/init.lua index c84a7e7..c045f13 100644 --- a/lua/argwrap/init.lua +++ b/lua/argwrap/init.lua @@ -28,8 +28,49 @@ function M.validate_range(range) end -- Compare two ranges relative to cursor position +-- Returns 1 if range1 is closer/smaller, -1 if range2 is closer/smaller, 0 if equal function M.compare_ranges(range1, range2) local _, line, col, _ = unpack(fn.getpos('.')) + + -- Check if we have complete range information (lineEnd and colEnd) + local hasCompleteRange1 = range1.lineEnd and range1.colEnd + local hasCompleteRange2 = range2.lineEnd and range2.colEnd + + -- If both ranges are complete and contain the cursor, prefer the smaller (more nested) one + if hasCompleteRange1 and hasCompleteRange2 then + -- Check if cursor is inside each range + local inside1 = (range1.lineStart < line or (range1.lineStart == line and range1.colStart <= col)) + and (range1.lineEnd > line or (range1.lineEnd == line and range1.colEnd >= col)) + local inside2 = (range2.lineStart < line or (range2.lineStart == line and range2.colStart <= col)) + and (range2.lineEnd > line or (range2.lineEnd == line and range2.colEnd >= col)) + + if inside1 and inside2 then + -- Both ranges contain cursor, prefer the smaller (more nested) one + local size1 = range1.lineEnd - range1.lineStart + local size2 = range2.lineEnd - range2.lineStart + + if size1 < size2 then + return 1 -- range1 is smaller (more nested) + elseif size1 > size2 then + return -1 -- range2 is smaller (more nested) + else + -- Same line span, compare column span + local colSize1 = range1.colEnd - range1.colStart + local colSize2 = range2.colEnd - range2.colStart + if colSize1 < colSize2 then + return 1 + elseif colSize1 > colSize2 then + return -1 + end + end + elseif inside1 then + return 1 -- Only range1 contains cursor + elseif inside2 then + return -1 -- Only range2 contains cursor + end + end + + -- Use original distance-based logic (for incomplete ranges or when cursor is outside) local lineDiff1 = range1.lineStart - line local colDiff1 = range1.colStart - col local lineDiff2 = range2.lineStart - line diff --git a/tests/test_argwrap.lua b/tests/test_argwrap.lua index 4d47e56..488336c 100644 --- a/tests/test_argwrap.lua +++ b/tests/test_argwrap.lua @@ -550,6 +550,107 @@ describe('argwrap', function() local range = argwrap.find_closest_range() assert.same({}, range) end) + + describe('find_closest_range with nested structures', function() + it('should find innermost range when cursor is inside nested braces', function() + vim.fn.setline(1, { + '{', + ' alala = {', + " 'asdf',", + " 'asdf'", + ' }', + ' azaza(asdf,asdf) {', + " 'zzxcv'", + ' }', + '}', + }) + -- Place cursor inside azaza's parentheses (line 6, after the opening paren) + vim.fn.setpos('.', { 0, 6, 10, 0 }) + local range = argwrap.find_closest_range() + + -- Should find the () range, not the outer {} ranges + assert.equals(6, range.lineStart) + -- The opening paren '(' should be around column 9 + assert.is_true(range.colStart >= 8 and range.colStart <= 10) + end) + + it('should prefer parentheses over outer braces when cursor is at function args', function() + vim.fn.setline(1, { + 'config = {', + ' func(arg1, arg2)', + '}', + }) + -- Place cursor inside func's arguments + vim.fn.setpos('.', { 0, 2, 10, 0 }) + local range = argwrap.find_closest_range() + + -- Should find the () range at line 2, not the {} at line 1 + assert.equals(2, range.lineStart) + assert.equals(2, range.lineEnd) + end) + + it('should handle toggle on nested function arguments correctly', function() + vim.fn.setline(1, { + '{', + ' azaza(asdf, qwer) {', + " 'zzxcv'", + ' }', + '}', + }) + -- Place cursor inside azaza's arguments + vim.fn.setpos('.', { 0, 2, 12, 0 }) + argwrap.toggle() + + -- Should wrap only the function arguments, not the entire structure + local line2 = vim.fn.getline(2) + local line3 = vim.fn.getline(3) + + -- Line 2 should now be the opening with function name and paren + assert.is_true(line2:match('azaza') ~= nil) + -- Should have wrapped the arguments (line 3 should be an argument, not the original content) + assert.is_true(line3:match('asdf') ~= nil or line3:match('qwer') ~= nil) + -- Original line 3 content should have moved down + assert.is_true(vim.fn.getline(5):match('zzxcv') ~= nil or vim.fn.getline(6):match('zzxcv') ~= nil) + end) + end) + + -- Also add a test in the describe('compare_ranges') section to verify the fix: + + describe('compare_ranges with nested structures', function() + it('should prefer closer range with smaller absolute distance', function() + vim.fn.setline(1, { + '{', + ' func(a, b)', + '}', + }) + -- Cursor at line 2, col 10 (inside the parentheses) + vim.fn.setpos('.', { 0, 2, 10, 0 }) + + -- Inner range: parentheses at line 2 + local innerRange = { lineStart = 2, colStart = 7, lineEnd = 2, colEnd = 12 } + -- Outer range: braces at line 1 + local outerRange = { lineStart = 1, colStart = 1, lineEnd = 3, colEnd = 1 } + + -- Inner range should be considered closer (return 1) + local result = argwrap.compare_ranges(innerRange, outerRange) + assert.equals(1, result) + end) + + it('should handle ranges on same line by column distance', function() + vim.fn.setline(1, 'func1(a) + func2(b)') + -- Cursor at col 7 (inside func1's args) + vim.fn.setpos('.', { 0, 1, 7, 0 }) + + -- First function's parens + local range1 = { lineStart = 1, colStart = 6, lineEnd = 1, colEnd = 8 } + -- Second function's parens + local range2 = { lineStart = 1, colStart = 17, lineEnd = 1, colEnd = 19 } + + -- range1 should be closer (return 1) + local result = argwrap.compare_ranges(range1, range2) + assert.equals(1, result) + end) + end) end) describe('toggle', function()