feat: Improve `move_cursor`. (#334)
Adds "sticky" option for `move_cursor`, making the cursor "stick" to the text as the buffer gets modified.
This commit is contained in:
parent
ae876ab0f8
commit
ef592b5c4c
|
@ -613,16 +613,29 @@ configured separately. The default highlight group used is `Visual`:
|
|||
--------------------------------------------------------------------------------
|
||||
3.6. Cursor *nvim-surround.config.move_cursor*
|
||||
|
||||
By default, when a surround action is performed, the cursor moves to the
|
||||
beginning of the action.
|
||||
By default (or when `move_cursor = "begin"`), when a surround action is
|
||||
performed, the cursor moves to the beginning of the action.
|
||||
|
||||
Old text Command New text ~
|
||||
some_t*ext ysiw[ *[ some_text ]
|
||||
another { sample *} ds{ another *sample
|
||||
(hello* world) csbB *{hello world}
|
||||
|
||||
This behavior can be disabled by setting `move_cursor = false` in one of the
|
||||
setup functions.
|
||||
If `move_cursor` is set to `"sticky"`, the cursor will "stick" to the current
|
||||
character, and move with the text as the buffer changes.
|
||||
|
||||
Old text Command New text ~
|
||||
some_t*ext ysiw[ [ some_t*ext ]
|
||||
another { sample *} ds{ another sampl*e
|
||||
(hello* world) csbffoo<CR> foo(hello* world)
|
||||
|
||||
If `move_cursor` is set to `false`, the cursor won't move at all, regardless
|
||||
of how the buffer changes.
|
||||
|
||||
Old text Command New text ~
|
||||
some_t*ext ysiw[ [ some_*text ]
|
||||
another { *sample } ds{ another sa*mple
|
||||
(hello* world) csbffoo<CR> foo(he*llo world)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
3.7. Indentation *nvim-surround.config.indent_lines*
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
---@field surrounds table<string, surround>
|
||||
---@field aliases table<string, string|string[]>
|
||||
---@field highlight { duration: integer }
|
||||
---@field move_cursor false|"begin"|"end"
|
||||
---@field move_cursor false|"begin"|"sticky"
|
||||
---@field indent_lines function
|
||||
|
||||
--[====================================================================================================================[
|
||||
|
@ -58,5 +58,5 @@
|
|||
---@field surrounds? table<string, false|user_surround>
|
||||
---@field aliases? table<string, false|string|string[]>
|
||||
---@field highlight? { duration: false|integer }
|
||||
---@field move_cursor? false|"begin"|"end"
|
||||
---@field move_cursor? false|"begin"|"sticky"
|
||||
---@field indent_lines? false|function
|
||||
|
|
|
@ -2,6 +2,11 @@ local config = require("nvim-surround.config")
|
|||
|
||||
local M = {}
|
||||
|
||||
M.namespace = {
|
||||
highlight = vim.api.nvim_create_namespace("nvim-surround-highlight"),
|
||||
extmark = vim.api.nvim_create_namespace("nvim-surround-extmark"),
|
||||
}
|
||||
|
||||
--[====================================================================================================================[
|
||||
Cursor helper functions
|
||||
--]====================================================================================================================]
|
||||
|
@ -24,11 +29,12 @@ M.set_curpos = function(pos)
|
|||
end
|
||||
|
||||
-- Move the cursor to a location in the buffer, depending on the `move_cursor` setting.
|
||||
---@param pos { first_pos: position, old_pos: position } Various positions in the buffer.
|
||||
---@param pos { first_pos: position, sticky_pos: position, old_pos: position } Various positions in the buffer.
|
||||
M.restore_curpos = function(pos)
|
||||
-- TODO: Add a `last_pos` field for if `move_cursor` is set to "end"
|
||||
if config.get_opts().move_cursor == "begin" then
|
||||
M.set_curpos(pos.first_pos)
|
||||
elseif config.get_opts().move_cursor == "sticky" then
|
||||
M.set_curpos(pos.sticky_pos)
|
||||
elseif not config.get_opts().move_cursor then
|
||||
M.set_curpos(pos.old_pos)
|
||||
end
|
||||
|
@ -117,6 +123,29 @@ M.set_operator_marks = function(motion)
|
|||
M.set_mark(">", visual_marks[2])
|
||||
end
|
||||
|
||||
-- Gets extmark position for the current buffer.
|
||||
---@param extmark integer The extmark ID number.
|
||||
---@return position @The position of the extmark in the buffer.
|
||||
---@nodiscard
|
||||
M.get_extmark = function(extmark)
|
||||
local pos = vim.api.nvim_buf_get_extmark_by_id(0, M.namespace.extmark, extmark, {})
|
||||
return { pos[1] + 1, pos[2] + 1 }
|
||||
end
|
||||
|
||||
-- Creates an extmark for the given position.
|
||||
---@param pos position The position in the buffer.
|
||||
---@return integer @The extmark ID.
|
||||
---@nodiscard
|
||||
M.set_extmark = function(pos)
|
||||
return vim.api.nvim_buf_set_extmark(0, M.namespace.extmark, pos[1] - 1, pos[2] - 1, {})
|
||||
end
|
||||
|
||||
-- Deletes an extmark from the buffer.
|
||||
---@param extmark integer The extmark ID number.
|
||||
M.del_extmark = function(extmark)
|
||||
vim.api.nvim_buf_del_extmark(0, M.namespace.extmark, extmark)
|
||||
end
|
||||
|
||||
--[====================================================================================================================[
|
||||
Byte indexing helper functions
|
||||
--]====================================================================================================================]
|
||||
|
@ -257,11 +286,10 @@ M.highlight_selection = function(selection)
|
|||
if not selection then
|
||||
return
|
||||
end
|
||||
local namespace = vim.api.nvim_create_namespace("NvimSurround")
|
||||
|
||||
vim.highlight.range(
|
||||
0,
|
||||
namespace,
|
||||
M.namespace.highlight,
|
||||
"NvimSurroundHighlight",
|
||||
{ selection.first_pos[1] - 1, selection.first_pos[2] - 1 },
|
||||
{ selection.last_pos[1] - 1, selection.last_pos[2] - 1 },
|
||||
|
@ -273,8 +301,7 @@ end
|
|||
|
||||
-- Clears all nvim-surround highlights for the buffer.
|
||||
M.clear_highlights = function()
|
||||
local namespace = vim.api.nvim_create_namespace("NvimSurround")
|
||||
vim.api.nvim_buf_clear_namespace(0, namespace, 0, -1)
|
||||
vim.api.nvim_buf_clear_namespace(0, M.namespace.highlight, 0, -1)
|
||||
-- Force the screen to clear the highlight immediately
|
||||
vim.cmd.redraw()
|
||||
end
|
||||
|
|
|
@ -64,12 +64,16 @@ M.normal_surround = function(args)
|
|||
local first_pos = args.selection.first_pos
|
||||
local last_pos = { args.selection.last_pos[1], args.selection.last_pos[2] + 1 }
|
||||
|
||||
local sticky_mark = buffer.set_extmark(M.normal_curpos)
|
||||
buffer.insert_text(last_pos, args.delimiters[2])
|
||||
buffer.insert_text(first_pos, args.delimiters[1])
|
||||
|
||||
buffer.restore_curpos({
|
||||
first_pos = first_pos,
|
||||
sticky_pos = buffer.get_extmark(sticky_mark),
|
||||
old_pos = M.normal_curpos,
|
||||
})
|
||||
buffer.del_extmark(sticky_mark)
|
||||
|
||||
if args.line_mode then
|
||||
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)
|
|||
return
|
||||
end
|
||||
|
||||
local sticky_mark = buffer.set_extmark(args.curpos)
|
||||
if vim.fn.visualmode() == "\22" then -- Visual block mode case (add delimiters to every line)
|
||||
if vim.o.selection == "exclusive" then
|
||||
last_pos[2] = last_pos[2] - 1
|
||||
|
@ -144,8 +149,10 @@ M.visual_surround = function(args)
|
|||
config.get_opts().indent_lines(first_pos[1], last_pos[1] + #delimiters[1] + #delimiters[2] - 2)
|
||||
buffer.restore_curpos({
|
||||
first_pos = first_pos,
|
||||
sticky_pos = buffer.get_extmark(sticky_mark),
|
||||
old_pos = args.curpos,
|
||||
})
|
||||
buffer.del_extmark(sticky_mark)
|
||||
end
|
||||
|
||||
-- Delete a surrounding delimiter pair, if it exists.
|
||||
|
@ -165,17 +172,21 @@ M.delete_surround = function(args)
|
|||
local selections = utils.get_nearest_selections(args.del_char, "delete")
|
||||
|
||||
if selections then
|
||||
local sticky_mark = buffer.set_extmark(args.curpos)
|
||||
-- Delete the right selection first to ensure selection positions are correct
|
||||
buffer.delete_selection(selections.right)
|
||||
buffer.delete_selection(selections.left)
|
||||
|
||||
config.get_opts().indent_lines(
|
||||
selections.left.first_pos[1],
|
||||
selections.left.first_pos[1] + selections.right.first_pos[1] - selections.left.last_pos[1]
|
||||
)
|
||||
buffer.restore_curpos({
|
||||
first_pos = selections.left.first_pos,
|
||||
sticky_pos = buffer.get_extmark(sticky_mark),
|
||||
old_pos = args.curpos,
|
||||
})
|
||||
buffer.del_extmark(sticky_mark)
|
||||
end
|
||||
|
||||
cache.set_callback("v:lua.require'nvim-surround'.delete_callback")
|
||||
|
@ -221,13 +232,16 @@ M.change_surround = function(args)
|
|||
selections.right.first_pos[2] = space_end + 1
|
||||
end
|
||||
|
||||
local sticky_mark = buffer.set_extmark(args.curpos)
|
||||
-- Change the right selection first to ensure selection positions are correct
|
||||
buffer.change_selection(selections.right, delimiters[2])
|
||||
buffer.change_selection(selections.left, delimiters[1])
|
||||
buffer.restore_curpos({
|
||||
first_pos = selections.left.first_pos,
|
||||
sticky_pos = buffer.get_extmark(sticky_mark),
|
||||
old_pos = args.curpos,
|
||||
})
|
||||
buffer.del_extmark(sticky_mark)
|
||||
|
||||
if args.line_mode then
|
||||
local first_pos = selections.left.first_pos
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
local cr = vim.api.nvim_replace_termcodes("<CR>", true, false, true)
|
||||
local esc = vim.api.nvim_replace_termcodes("<Esc>", true, false, true)
|
||||
local ctrl_v = vim.api.nvim_replace_termcodes("<C-v>", true, false, true)
|
||||
local get_curpos = function()
|
||||
local curpos = vim.api.nvim_win_get_cursor(0)
|
||||
return { curpos[1], curpos[2] + 1 }
|
||||
|
@ -7,12 +8,18 @@ end
|
|||
local set_curpos = function(pos)
|
||||
vim.api.nvim_win_set_cursor(0, { pos[1], pos[2] - 1 })
|
||||
end
|
||||
local check_curpos = function(pos)
|
||||
assert.are.same({ pos[1], pos[2] - 1 }, vim.api.nvim_win_get_cursor(0))
|
||||
end
|
||||
local set_lines = function(lines)
|
||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
|
||||
end
|
||||
local check_lines = function(lines)
|
||||
assert.are.same(lines, vim.api.nvim_buf_get_lines(0, 0, -1, false))
|
||||
end
|
||||
local get_extmarks = function()
|
||||
return vim.api.nvim_buf_get_extmarks(0, require("nvim-surround.buffer").namespace.extmark, 0, -1, {})
|
||||
end
|
||||
|
||||
describe("configuration", function()
|
||||
before_each(function()
|
||||
|
@ -21,7 +28,7 @@ describe("configuration", function()
|
|||
end)
|
||||
|
||||
it("can define own add mappings", function()
|
||||
require("nvim-surround").setup({
|
||||
require("nvim-surround").buffer_setup({
|
||||
surrounds = {
|
||||
["1"] = { add = { "1", "1" } },
|
||||
["2"] = { add = { "2", { "2" } } },
|
||||
|
@ -75,7 +82,7 @@ describe("configuration", function()
|
|||
end)
|
||||
|
||||
it("can define and use 'interpreted' multi-byte mappings", function()
|
||||
require("nvim-surround").setup({
|
||||
require("nvim-surround").buffer_setup({
|
||||
surrounds = {
|
||||
-- interpreted multi-byte
|
||||
["<M-]>"] = {
|
||||
|
@ -95,7 +102,7 @@ describe("configuration", function()
|
|||
end)
|
||||
|
||||
it("default deletes using invalid_key_behavior for an 'interpreted' multi-byte mapping", function()
|
||||
require("nvim-surround").setup({
|
||||
require("nvim-surround").buffer_setup({
|
||||
surrounds = {
|
||||
-- interpreted multi-byte
|
||||
["<C-q>"] = {
|
||||
|
@ -114,7 +121,7 @@ describe("configuration", function()
|
|||
end)
|
||||
|
||||
it("can disable surrounds", function()
|
||||
require("nvim-surround").setup({
|
||||
require("nvim-surround").buffer_setup({
|
||||
surrounds = {
|
||||
["("] = false,
|
||||
},
|
||||
|
@ -130,7 +137,7 @@ describe("configuration", function()
|
|||
end)
|
||||
|
||||
it("can change invalid_key_behavior", function()
|
||||
require("nvim-surround").setup({
|
||||
require("nvim-surround").buffer_setup({
|
||||
surrounds = {
|
||||
invalid_key_behavior = {
|
||||
add = function(char)
|
||||
|
@ -150,7 +157,7 @@ describe("configuration", function()
|
|||
end)
|
||||
|
||||
it("can disable indent_lines", function()
|
||||
require("nvim-surround").setup({
|
||||
require("nvim-surround").buffer_setup({
|
||||
indent_lines = false,
|
||||
})
|
||||
|
||||
|
@ -166,7 +173,7 @@ describe("configuration", function()
|
|||
end)
|
||||
|
||||
it("can disable invalid_key_behavior", function()
|
||||
require("nvim-surround").setup({
|
||||
require("nvim-surround").buffer_setup({
|
||||
surrounds = {
|
||||
invalid_key_behavior = false,
|
||||
},
|
||||
|
@ -270,6 +277,224 @@ describe("configuration", function()
|
|||
})
|
||||
end)
|
||||
|
||||
it("can make the cursor 'stick' to the text (normal)", function()
|
||||
require("nvim-surround").buffer_setup({
|
||||
move_cursor = "sticky",
|
||||
surrounds = {
|
||||
["c"] = { add = { "singleline", "surr" } },
|
||||
["d"] = { add = { { "multiline", "f" }, "" } },
|
||||
["e"] = { add = { { "multiline", "f" }, { "", "shouldbethislength" } } },
|
||||
["f"] = { add = { "singleline", { "", "multilinehere" } } },
|
||||
},
|
||||
})
|
||||
|
||||
-- Sticks to the text if the cursor is inside the selection
|
||||
set_lines({
|
||||
"this is a line",
|
||||
})
|
||||
set_curpos({ 1, 9 })
|
||||
vim.cmd("normal ysiwc")
|
||||
check_curpos({ 1, 19 })
|
||||
|
||||
set_lines({
|
||||
"this is a line",
|
||||
})
|
||||
set_curpos({ 1, 4 })
|
||||
vim.cmd("normal ysiwd")
|
||||
check_curpos({ 2, 5 })
|
||||
|
||||
set_lines({
|
||||
"this is another line",
|
||||
})
|
||||
set_curpos({ 1, 14 })
|
||||
vim.cmd("normal ysiwe")
|
||||
check_curpos({ 2, 7 })
|
||||
|
||||
set_lines({
|
||||
"this is a line",
|
||||
})
|
||||
set_curpos({ 1, 9 })
|
||||
vim.cmd("normal ysiwf")
|
||||
check_curpos({ 1, 19 })
|
||||
|
||||
-- Doesn't move if the cursor is before the selection
|
||||
set_lines({
|
||||
"this 'is' a line",
|
||||
})
|
||||
set_curpos({ 1, 2 })
|
||||
vim.cmd("normal ysa'c")
|
||||
vim.cmd("normal ysa'd")
|
||||
vim.cmd("normal ysa'e")
|
||||
vim.cmd("normal ysa'f")
|
||||
check_curpos({ 1, 2 })
|
||||
|
||||
assert.are.same(get_extmarks(), {})
|
||||
end)
|
||||
|
||||
it("can make the cursor 'stick' to the text (visual)", function()
|
||||
require("nvim-surround").buffer_setup({
|
||||
move_cursor = "sticky",
|
||||
})
|
||||
|
||||
set_lines({
|
||||
"this is a line",
|
||||
})
|
||||
set_curpos({ 1, 9 })
|
||||
vim.cmd("normal vllS'")
|
||||
check_curpos({ 1, 12 })
|
||||
|
||||
set_lines({
|
||||
"this is a line",
|
||||
"with some more text",
|
||||
})
|
||||
set_curpos({ 1, 6 })
|
||||
vim.cmd("normal vjeSb")
|
||||
check_curpos({ 2, 9 })
|
||||
|
||||
set_lines({
|
||||
"this is a line",
|
||||
"with some more text",
|
||||
})
|
||||
set_curpos({ 1, 6 })
|
||||
vim.cmd("normal vjeoSb")
|
||||
check_curpos({ 1, 7 })
|
||||
|
||||
assert.are.same(get_extmarks(), {})
|
||||
end)
|
||||
|
||||
it("can make the cursor 'stick' to the text (visual line)", function()
|
||||
require("nvim-surround").buffer_setup({
|
||||
move_cursor = "sticky",
|
||||
})
|
||||
|
||||
set_lines({
|
||||
"this is a line",
|
||||
})
|
||||
set_curpos({ 1, 9 })
|
||||
vim.cmd("normal VSb")
|
||||
check_curpos({ 2, 9 })
|
||||
|
||||
set_lines({
|
||||
"this is a line",
|
||||
"with some more text",
|
||||
})
|
||||
set_curpos({ 1, 6 })
|
||||
vim.cmd("normal VjStdiv" .. cr)
|
||||
check_curpos({ 3, 6 })
|
||||
|
||||
assert.are.same(get_extmarks(), {})
|
||||
end)
|
||||
|
||||
it("can make the cursor 'stick' to the text (visual block)", function()
|
||||
require("nvim-surround").buffer_setup({
|
||||
move_cursor = "sticky",
|
||||
surrounds = {
|
||||
["x"] = {
|
||||
add = { { "|", "" }, { "", "|" } },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
set_lines({
|
||||
"this is a line",
|
||||
"this is another line",
|
||||
})
|
||||
set_curpos({ 1, 5 })
|
||||
vim.cmd("normal! " .. ctrl_v .. "jf ")
|
||||
vim.cmd("normal Sb")
|
||||
check_curpos({ 2, 9 })
|
||||
|
||||
set_lines({
|
||||
"this is a line",
|
||||
"this is another line",
|
||||
"some more random text",
|
||||
})
|
||||
set_curpos({ 1, 4 })
|
||||
vim.cmd("normal! " .. ctrl_v .. "jjww")
|
||||
vim.cmd("normal Sx")
|
||||
set_curpos({ 8, 8 })
|
||||
|
||||
assert.are.same(get_extmarks(), {})
|
||||
end)
|
||||
|
||||
it("can make the cursor 'stick' to the text (delete)", function()
|
||||
require("nvim-surround").buffer_setup({
|
||||
move_cursor = "sticky",
|
||||
})
|
||||
|
||||
set_lines({
|
||||
"func_name(foobar)",
|
||||
})
|
||||
set_curpos({ 1, 14 })
|
||||
vim.cmd("normal dsf")
|
||||
check_curpos({ 1, 4 })
|
||||
|
||||
set_lines({
|
||||
"<div id='foobar'>",
|
||||
" hello",
|
||||
"</div>",
|
||||
})
|
||||
set_curpos({ 2, 7 })
|
||||
vim.cmd("normal dst")
|
||||
check_curpos({ 2, 7 })
|
||||
|
||||
set_lines({
|
||||
"hello 'world'",
|
||||
})
|
||||
set_curpos({ 1, 2 })
|
||||
vim.cmd("normal dsq")
|
||||
check_curpos({ 1, 2 })
|
||||
|
||||
set_lines({
|
||||
"func(hello) world",
|
||||
})
|
||||
set_curpos({ 1, 14 })
|
||||
vim.cmd("normal dsf")
|
||||
check_curpos({ 1, 8 })
|
||||
|
||||
assert.are.same(get_extmarks(), {})
|
||||
end)
|
||||
|
||||
it("can make the cursor 'stick' to the text (change)", function()
|
||||
require("nvim-surround").buffer_setup({
|
||||
move_cursor = "sticky",
|
||||
})
|
||||
|
||||
set_lines({
|
||||
"func_name(foobar)",
|
||||
})
|
||||
set_curpos({ 1, 14 })
|
||||
vim.cmd("normal csff" .. cr)
|
||||
check_curpos({ 1, 6 })
|
||||
|
||||
set_lines({
|
||||
"<div id='foobar'>",
|
||||
" hello",
|
||||
"</div>",
|
||||
})
|
||||
set_curpos({ 2, 7 })
|
||||
vim.cmd("normal csth1" .. cr)
|
||||
check_curpos({ 2, 7 })
|
||||
vim.cmd("normal csTbutton" .. cr)
|
||||
check_curpos({ 2, 7 })
|
||||
|
||||
set_lines({
|
||||
"hello 'world'",
|
||||
})
|
||||
set_curpos({ 1, 2 })
|
||||
vim.cmd("normal csqffoobar" .. cr)
|
||||
check_curpos({ 1, 2 })
|
||||
|
||||
set_lines({
|
||||
"<div className='container'>hello</div> world",
|
||||
})
|
||||
set_curpos({ 1, 41 })
|
||||
vim.cmd("normal csTb" .. cr)
|
||||
check_curpos({ 1, 15 })
|
||||
|
||||
assert.are.same(get_extmarks(), {})
|
||||
end)
|
||||
|
||||
it("can partially define surrounds", function()
|
||||
require("nvim-surround").buffer_setup({
|
||||
surrounds = {
|
||||
|
|
Loading…
Reference in New Issue