Skip to content

Commit ef592b5

Browse files
authored
feat: Improve move_cursor. (#334)
Adds "sticky" option for `move_cursor`, making the cursor "stick" to the text as the buffer gets modified.
1 parent ae876ab commit ef592b5

File tree

5 files changed

+298
-19
lines changed

5 files changed

+298
-19
lines changed

doc/nvim-surround.txt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -613,16 +613,29 @@ configured separately. The default highlight group used is `Visual`:
613613
--------------------------------------------------------------------------------
614614
3.6. Cursor *nvim-surround.config.move_cursor*
615615

616-
By default, when a surround action is performed, the cursor moves to the
617-
beginning of the action.
616+
By default (or when `move_cursor = "begin"`), when a surround action is
617+
performed, the cursor moves to the beginning of the action.
618618

619619
Old text Command New text ~
620620
some_t*ext ysiw[ *[ some_text ]
621621
another { sample *} ds{ another *sample
622622
(hello* world) csbB *{hello world}
623623

624-
This behavior can be disabled by setting `move_cursor = false` in one of the
625-
setup functions.
624+
If `move_cursor` is set to `"sticky"`, the cursor will "stick" to the current
625+
character, and move with the text as the buffer changes.
626+
627+
Old text Command New text ~
628+
some_t*ext ysiw[ [ some_t*ext ]
629+
another { sample *} ds{ another sampl*e
630+
(hello* world) csbffoo<CR> foo(hello* world)
631+
632+
If `move_cursor` is set to `false`, the cursor won't move at all, regardless
633+
of how the buffer changes.
634+
635+
Old text Command New text ~
636+
some_t*ext ysiw[ [ some_*text ]
637+
another { *sample } ds{ another sa*mple
638+
(hello* world) csbffoo<CR> foo(he*llo world)
626639

627640
--------------------------------------------------------------------------------
628641
3.7. Indentation *nvim-surround.config.indent_lines*

lua/nvim-surround/annotations.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
---@field surrounds table<string, surround>
3636
---@field aliases table<string, string|string[]>
3737
---@field highlight { duration: integer }
38-
---@field move_cursor false|"begin"|"end"
38+
---@field move_cursor false|"begin"|"sticky"
3939
---@field indent_lines function
4040

4141
--[====================================================================================================================[
@@ -58,5 +58,5 @@
5858
---@field surrounds? table<string, false|user_surround>
5959
---@field aliases? table<string, false|string|string[]>
6060
---@field highlight? { duration: false|integer }
61-
---@field move_cursor? false|"begin"|"end"
61+
---@field move_cursor? false|"begin"|"sticky"
6262
---@field indent_lines? false|function

lua/nvim-surround/buffer.lua

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ local config = require("nvim-surround.config")
22

33
local M = {}
44

5+
M.namespace = {
6+
highlight = vim.api.nvim_create_namespace("nvim-surround-highlight"),
7+
extmark = vim.api.nvim_create_namespace("nvim-surround-extmark"),
8+
}
9+
510
--[====================================================================================================================[
611
Cursor helper functions
712
--]====================================================================================================================]
@@ -24,11 +29,12 @@ M.set_curpos = function(pos)
2429
end
2530

2631
-- Move the cursor to a location in the buffer, depending on the `move_cursor` setting.
27-
---@param pos { first_pos: position, old_pos: position } Various positions in the buffer.
32+
---@param pos { first_pos: position, sticky_pos: position, old_pos: position } Various positions in the buffer.
2833
M.restore_curpos = function(pos)
29-
-- TODO: Add a `last_pos` field for if `move_cursor` is set to "end"
3034
if config.get_opts().move_cursor == "begin" then
3135
M.set_curpos(pos.first_pos)
36+
elseif config.get_opts().move_cursor == "sticky" then
37+
M.set_curpos(pos.sticky_pos)
3238
elseif not config.get_opts().move_cursor then
3339
M.set_curpos(pos.old_pos)
3440
end
@@ -117,6 +123,29 @@ M.set_operator_marks = function(motion)
117123
M.set_mark(">", visual_marks[2])
118124
end
119125

126+
-- Gets extmark position for the current buffer.
127+
---@param extmark integer The extmark ID number.
128+
---@return position @The position of the extmark in the buffer.
129+
---@nodiscard
130+
M.get_extmark = function(extmark)
131+
local pos = vim.api.nvim_buf_get_extmark_by_id(0, M.namespace.extmark, extmark, {})
132+
return { pos[1] + 1, pos[2] + 1 }
133+
end
134+
135+
-- Creates an extmark for the given position.
136+
---@param pos position The position in the buffer.
137+
---@return integer @The extmark ID.
138+
---@nodiscard
139+
M.set_extmark = function(pos)
140+
return vim.api.nvim_buf_set_extmark(0, M.namespace.extmark, pos[1] - 1, pos[2] - 1, {})
141+
end
142+
143+
-- Deletes an extmark from the buffer.
144+
---@param extmark integer The extmark ID number.
145+
M.del_extmark = function(extmark)
146+
vim.api.nvim_buf_del_extmark(0, M.namespace.extmark, extmark)
147+
end
148+
120149
--[====================================================================================================================[
121150
Byte indexing helper functions
122151
--]====================================================================================================================]
@@ -257,11 +286,10 @@ M.highlight_selection = function(selection)
257286
if not selection then
258287
return
259288
end
260-
local namespace = vim.api.nvim_create_namespace("NvimSurround")
261289

262290
vim.highlight.range(
263291
0,
264-
namespace,
292+
M.namespace.highlight,
265293
"NvimSurroundHighlight",
266294
{ selection.first_pos[1] - 1, selection.first_pos[2] - 1 },
267295
{ selection.last_pos[1] - 1, selection.last_pos[2] - 1 },
@@ -273,8 +301,7 @@ end
273301

274302
-- Clears all nvim-surround highlights for the buffer.
275303
M.clear_highlights = function()
276-
local namespace = vim.api.nvim_create_namespace("NvimSurround")
277-
vim.api.nvim_buf_clear_namespace(0, namespace, 0, -1)
304+
vim.api.nvim_buf_clear_namespace(0, M.namespace.highlight, 0, -1)
278305
-- Force the screen to clear the highlight immediately
279306
vim.cmd.redraw()
280307
end

lua/nvim-surround/init.lua

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,16 @@ M.normal_surround = function(args)
6464
local first_pos = args.selection.first_pos
6565
local last_pos = { args.selection.last_pos[1], args.selection.last_pos[2] + 1 }
6666

67+
local sticky_mark = buffer.set_extmark(M.normal_curpos)
6768
buffer.insert_text(last_pos, args.delimiters[2])
6869
buffer.insert_text(first_pos, args.delimiters[1])
70+
6971
buffer.restore_curpos({
7072
first_pos = first_pos,
73+
sticky_pos = buffer.get_extmark(sticky_mark),
7174
old_pos = M.normal_curpos,
7275
})
76+
buffer.del_extmark(sticky_mark)
7377

7478
if args.line_mode then
7579
config.get_opts().indent_lines(first_pos[1], last_pos[1] + #args.delimiters[1] + #args.delimiters[2] - 2)
@@ -92,6 +96,7 @@ M.visual_surround = function(args)
9296
return
9397
end
9498

99+
local sticky_mark = buffer.set_extmark(args.curpos)
95100
if vim.fn.visualmode() == "\22" then -- Visual block mode case (add delimiters to every line)
96101
if vim.o.selection == "exclusive" then
97102
last_pos[2] = last_pos[2] - 1
@@ -144,8 +149,10 @@ M.visual_surround = function(args)
144149
config.get_opts().indent_lines(first_pos[1], last_pos[1] + #delimiters[1] + #delimiters[2] - 2)
145150
buffer.restore_curpos({
146151
first_pos = first_pos,
152+
sticky_pos = buffer.get_extmark(sticky_mark),
147153
old_pos = args.curpos,
148154
})
155+
buffer.del_extmark(sticky_mark)
149156
end
150157

151158
-- Delete a surrounding delimiter pair, if it exists.
@@ -165,17 +172,21 @@ M.delete_surround = function(args)
165172
local selections = utils.get_nearest_selections(args.del_char, "delete")
166173

167174
if selections then
175+
local sticky_mark = buffer.set_extmark(args.curpos)
168176
-- Delete the right selection first to ensure selection positions are correct
169177
buffer.delete_selection(selections.right)
170178
buffer.delete_selection(selections.left)
179+
171180
config.get_opts().indent_lines(
172181
selections.left.first_pos[1],
173182
selections.left.first_pos[1] + selections.right.first_pos[1] - selections.left.last_pos[1]
174183
)
175184
buffer.restore_curpos({
176185
first_pos = selections.left.first_pos,
186+
sticky_pos = buffer.get_extmark(sticky_mark),
177187
old_pos = args.curpos,
178188
})
189+
buffer.del_extmark(sticky_mark)
179190
end
180191

181192
cache.set_callback("v:lua.require'nvim-surround'.delete_callback")
@@ -221,13 +232,16 @@ M.change_surround = function(args)
221232
selections.right.first_pos[2] = space_end + 1
222233
end
223234

235+
local sticky_mark = buffer.set_extmark(args.curpos)
224236
-- Change the right selection first to ensure selection positions are correct
225237
buffer.change_selection(selections.right, delimiters[2])
226238
buffer.change_selection(selections.left, delimiters[1])
227239
buffer.restore_curpos({
228240
first_pos = selections.left.first_pos,
241+
sticky_pos = buffer.get_extmark(sticky_mark),
229242
old_pos = args.curpos,
230243
})
244+
buffer.del_extmark(sticky_mark)
231245

232246
if args.line_mode then
233247
local first_pos = selections.left.first_pos

0 commit comments

Comments
 (0)