Skip to content

Commit f929665

Browse files
authored
fix: Handle multi-byte characters for change/delete. (#318)
Resolves #317.
1 parent a4e30d3 commit f929665

File tree

4 files changed

+56
-21
lines changed

4 files changed

+56
-21
lines changed

lua/nvim-surround/buffer.lua

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,10 @@ M.get_first_byte = function(pos)
139139
end
140140

141141
-- Gets the position of the last byte of a character, according to the UTF-8 standard.
142-
---@param pos position|nil The position of the beginning of the character.
143-
---@return position|nil @The position of the last byte of the character.
142+
---@param pos position The position of the beginning of the character.
143+
---@return position @The position of the last byte of the character.
144144
---@nodiscard
145145
M.get_last_byte = function(pos)
146-
if not pos then
147-
return nil
148-
end
149-
150146
local byte = string.byte(M.get_line(pos[1]):sub(pos[2], pos[2]))
151147
if not byte then
152148
return pos

lua/nvim-surround/init.lua

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,6 @@ M.visual_surround = function(args)
130130
end
131131

132132
last_pos = buffer.get_last_byte(last_pos)
133-
if not last_pos then
134-
return
135-
end
136133
buffer.insert_text({ last_pos[1], last_pos[2] + 1 }, delimiters[2])
137134
buffer.insert_text(first_pos, delimiters[1])
138135
end
@@ -257,6 +254,7 @@ M.normal_callback = function(mode)
257254
buffer.set_mark("]", last_pos)
258255
end
259256
-- Move the last position to the last byte of the character, if necessary
257+
---@diagnostic disable-next-line
260258
buffer.set_mark("]", buffer.get_last_byte(buffer.get_mark("]")))
261259

262260
buffer.adjust_mark("[")

lua/nvim-surround/patterns.lua

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ M.pos_to_index = function(pos)
3030
return #table.concat(buffer.get_lines(1, pos[1] - 1), "\n") + pos[2] + 1
3131
end
3232

33+
-- Expands a selection to properly contain multi-byte characters.
34+
---@param selection selection The given selection.
35+
---@return selection @The adjusted selection, handling multi-byte characters.
36+
---@nodiscard
37+
M.adjust_selection = function(selection)
38+
selection.first_pos = buffer.get_first_byte(selection.first_pos)
39+
selection.last_pos = buffer.get_last_byte(selection.last_pos)
40+
return selection
41+
end
42+
3343
-- Returns a selection in the buffer based on a Lua pattern.
3444
---@param find string The Lua pattern to find in the buffer.
3545
---@return selection|nil @The closest selection matching the pattern, if any.
@@ -58,10 +68,10 @@ M.get_selection = function(find)
5868
if not b_first or not b_last then
5969
return a_first
6070
and a_last
61-
and {
71+
and M.adjust_selection({
6272
first_pos = M.index_to_pos(a_first),
6373
last_pos = M.index_to_pos(a_last),
64-
}
74+
})
6575
end
6676
-- Adjust the selection character-wise
6777
local start_col, end_col = cursor_index, b_first
@@ -83,24 +93,24 @@ M.get_selection = function(find)
8393
end
8494
-- If the cursor is inside the range then return it
8595
if b_last and b_first and b_last >= cursor_index then
86-
return {
96+
return M.adjust_selection({
8797
first_pos = M.index_to_pos(b_first),
8898
last_pos = M.index_to_pos(b_last),
89-
}
99+
})
90100
end
91101
-- Else if there's a range found after the cursor, return it
92102
if a_first and a_last then
93-
return {
103+
return M.adjust_selection({
94104
first_pos = M.index_to_pos(a_first),
95105
last_pos = M.index_to_pos(a_last),
96-
}
106+
})
97107
end
98108
-- Otherwise return the range found before the cursor, if one exists
99109
if b_first and b_last then
100-
return {
110+
return M.adjust_selection({
101111
first_pos = M.index_to_pos(b_first),
102112
last_pos = M.index_to_pos(b_last),
103-
}
113+
})
104114
end
105115
end
106116

@@ -142,14 +152,14 @@ M.get_selections = function(selection, pattern)
142152
local selections = {
143153
---@cast first_index integer
144154
---@cast last_index integer
145-
left = {
155+
left = M.adjust_selection({
146156
first_pos = M.index_to_pos(offset + first_index - left_len - 1),
147157
last_pos = M.index_to_pos(offset + first_index - 2),
148-
},
149-
right = {
158+
}),
159+
right = M.adjust_selection({
150160
first_pos = M.index_to_pos(offset + last_index - right_len - 1),
151161
last_pos = M.index_to_pos(offset + last_index - 2),
152-
},
162+
}),
153163
}
154164
-- Handle special case where the column is invalid
155165
if selections.left.last_pos[2] > #buffer.get_line(selections.left.last_pos[1]) then

tests/basics_spec.lua

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,37 @@ describe("nvim-surround", function()
614614
"。(。。。)。",
615615
"𐍈𐍈𐍈(𐍈𐍈𐍈𐍈)𐍈𐍈𐍈",
616616
})
617+
618+
require("nvim-surround").setup({
619+
surrounds = {
620+
["x"] = {
621+
add = { "", "" },
622+
find = "‘[^‘’]*’",
623+
},
624+
["y"] = {
625+
add = { "‘‘", "’’" },
626+
find = "‘‘[^‘’]*’’",
627+
delete = "^(‘‘)().-(’’)()$",
628+
},
629+
},
630+
})
631+
set_lines({
632+
"‘foo bar’",
633+
})
634+
set_curpos({ 1, 5 })
635+
vim.cmd("normal csx_")
636+
check_lines({
637+
"_foo bar_",
638+
})
639+
640+
set_lines({
641+
"‘‘foo bar baz’’",
642+
})
643+
set_curpos({ 1, 3 })
644+
vim.cmd("normal dsy")
645+
check_lines({
646+
"foo bar baz",
647+
})
617648
end)
618649

619650
it("can properly use line-wise surrounds", function()

0 commit comments

Comments
 (0)