Closed jake-stewart closed 3 days ago
This sounds great 👍🏻
I suppose perform
would be for performing a motion? Currently I tried out visualToCursors()
but it looks like the cursors always start at column 0. What I'm trying to do is press I
(capital i) to add them to the starts of visual lines, and A
for the ends.
This sounds great 👍🏻
I suppose
perform
would be for performing a motion? Currently I tried outvisualToCursors()
but it looks like the cursors always start at column 0. What I'm trying to do is pressI
(capital i) to add them to the starts of visual lines, andA
for the ends.
perform
would be a motion or function which applies to each cursor.
once i have the api out i imagine it would be as easy as mapping I
to visualToCursors()
and then perform('^')
.
api coming along nicely.
you will be able to access it through mc.action
, which provides a context.
this lets users perform multiple complex actions and queries, only redrawing the cursors & updating the undolist once the function finishes.
cursors will have metatables with many useful methods for querying and manipulating cursor state.
here is how the column transpose logic is written with the new api:
local function transposeCursors(direction)
mc.action(function(ctx)
ctx:forEach(function(cursor)
cursor:convertToSingleLines()
end)
local values = ctx:map(function(cursor)
return cursor:getVisualLines()[1]
end)
ctx:forEach(function(cursor, i)
local idx = ((i - direction - 1) % #values) + 1
cursor:perform('"_c' .. values[idx] .. TERM_CODES.ESC .. "v`<")
end)
ctx:rotateSelectedCursor(direction)
end)
end
it's a lot better.
will have this api out tomorrow
i've pushed changes to the api
branch. i'll test it over the next couple of days to make sure no bugs are introduced before moving to main.
@mikavilpas the changes you talked about in #6 can now be implemented like so:
vim.keymap.set("v", "I", function()
mc.visualToCursors()
mc.perform("^")
mc.feedkeys("I")
end)
vim.keymap.set("v", "A", function()
mc.visualToCursors()
mc.perform("$")
mc.feedkeys("A")
end)
vim.keymap.set("v", "<leader><esc>", function()
local mode = vim.fn.mode()
mc.visualToCursors()
if (mode == "V" or mode == "v") then
mc.perform("^")
end
end)
meanwhile, more low level things like match, select, align, transpose can all be (and all are) written with the new cursor api. here are some examples
function mc.transposeCursors(direction)
mc.action(function(ctx)
ctx:forEach(function(cursor)
cursor:convertToSingleLines()
end)
local values = ctx:map(function(cursor)
return cursor:getVisualLines()[1]
end)
ctx:forEach(function(cursor, i)
local idx = ((i - direction - 1) % #values) + 1
cursor:perform('"_c' .. values[idx] .. TERM_CODES.ESC .. "v`<o")
end)
ctx:findNextCursor(ctx:getMainCursor():getPos(), direction):select()
end)
end
function mc.matchCursors(pattern)
mc.action(function(ctx)
pattern = pattern or vim.fn.input("Match: ")
if not pattern or pattern == "" then
return
end
ctx:forEach(function(cursor)
cursor:convertToSingleLines()
end)
ctx:forEach(function(cursor)
local selection = cursor:getVisualLines()
local matches = matchlist(selection, pattern, { userConfig = true })
local visual = cursor:getVisual()
local line, col = table.unpack(visual[1])
for _, match in ipairs(matches) do
if #match.text > 0 then
local newCursor = cursor:clone()
newCursor:setVisual({
line,
col + match.byteidx + #match.text - 1,
line,
col + match.byteidx
})
newCursor:setMode("n")
end
end
cursor:delete()
end)
end)
end
still need to write up docs for this. currently i've added these functions but i will probably need more and may rename them.
--- @return number
function Cursor:line()
--- @return number
function Cursor:col()
--- @return string
function Cursor:getLine()
function Cursor:delete()
-- sets the main cursor to this one
function Cursor:select()
--- @return boolean
function Cursor:atVisualStart()
-- creates a new cursor for each visual line and deletes this one
function Cursor:convertToSingleLines()
--- @return boolean
function Cursor:isMainCursor()
--- @return [integer, integer]
function Cursor:getPos()
--- @param pos [integer, integer]
function Cursor:setPos(pos)
--- @return Cursor
function Cursor:clone()
--- @return string[]
function Cursor:getVisualLines()
--- @return string[]
function Cursor:getFullVisualLines()
--- @return string
function Cursor:mode()
--- @param mode string
function Cursor:setMode(mode)
--- @param action string | function
--- @param opts? { remap: boolean }
function Cursor:perform(action, opts)
--- @param visual [integer, integer, integer, integer]
function Cursor:setVisual(visual)
--- @return boolean
function Cursor:inVisualMode()
meanwhile the CursorContext
has methods like forEach
, map
, getMainCursor
, getFirstCursor
, getLastCursor
. again subject to change
The new api is excellent, thanks a lot!
One question though, is it possible to make the multi cursors enter insert mode? So far I did not find a way to do this, but other than this everything was 99% less code :)
The new api is excellent, thanks a lot!
One question though, is it possible to make the multi cursors enter insert mode? So far I did not find a way to do this, but other than this everything was 99% less code :)
insert mode is tricky since the way it is considered a single motion (it is dot repeatable). i record the keys and then play them for each cursor. therefore it doesn't make much sense to tell a cursor to enter insert mode and stay in it programatically.
instead, it's better to use the full cursor:perform("ihello" .. esc)
or, you can use the mc.feedkeys("ihello")
which will queue up ihello
for the cursors, applying it to each once you leave insert mode. like shown below:
vim.keymap.set("v", "I", function()
mc.visualToCursors()
mc.perform("^")
mc.feedkeys("I")
end)
could you explain what you are trying to achieve if neither of these work for you, thanks.
i am still working on the api. it now supports getCursors
, instead of forcing you to use map
/forEach
. i will probably my changes tonight.
Sure - what I'm trying to do is the same effect you get in visual block mode with capital I:
https://github.com/user-attachments/assets/7257d679-8c09-4cfe-8b00-8032f3ec7a87
Here I have it working but I have to add a i
manually to go to insert mode now. It's not the end of the world though, so if there's no easy way of fixing it, I think it can also be left unfixed.
@mikavilpas maybe i explained myself poorly. does this achieve what you're after?
local CTRL_V = vim.api.nvim_replace_termcodes("<c-v>", true, true, true);
vim.keymap.set("v", "I", function()
local mode = vim.fn.mode()
mc.visualToCursors()
if mode == CTRL_V then
mc.feedkeys("i")
else
mc.perform("^")
mc.feedkeys("I")
end
end)
also for this task i prefer just doing
vim.keymap.set({"n", "v"}, "<down>", function() mc.addCursor("j") end)
and then just hit <down>
a bunch of times. for me it's mapped to <c-j>
it is now merged into main. readme has documentation.
also @mikavilpas i added mc.appendVisual
and mc.insertVisual
vim.keymap.set("v", "I", mc.insertVisual)
vim.keymap.set("v", "A", mc.appendVisual)
need an api so power users like @mikavilpas can write match/split/align functionality easily in their configs
will expand and refine this list later