Skip to content

Commit

Permalink
feat: add action start-multiple-cursors (#1547)
Browse files Browse the repository at this point in the history
This action can be used to create multiple selections in vscode

refactor: use action for builtin multi-cursor feature
  • Loading branch information
xiyaowong committed Oct 18, 2023
1 parent fa22f20 commit f4a6ac5
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 72 deletions.
92 changes: 55 additions & 37 deletions runtime/lua/vscode-neovim/cursor.lua
Original file line number Diff line number Diff line change
@@ -1,64 +1,82 @@
local api = vim.api

local vscode = require("vscode-neovim.api")
local util = require("vscode-neovim.util")

-- this module is responsible for creating multiple cursors, triggering a visual update, and displaying the fake visual cursor
local M = {}

-- ------------------------------ multi cursor ------------------------------ --
M.should_notify_multi_cursor = nil
M.multi_cursor_visual_mode = nil
M.multi_cursor_append = nil
M.multi_cursor_skip_empty = nil

function M.prepare_multi_cursor(append, skip_empty)
local m = vim.fn.mode()
if m == "V" or m == "\x16" then
M.should_notify_multi_cursor = true
M.multi_cursor_visual_mode = m
M.multi_cursor_append = append
M.multi_cursor_skip_empty = skip_empty
-- We need to start insert, then spawn cursors otherwise they'll be destroyed
-- using feedkeys() here because :startinsert is being delayed
vim.cmd([[ call feedkeys("\<Esc>i", 'n') ]])
local multi_cursor_task

local function start_multi_cursor(right, skip_empty)
multi_cursor_task = nil
local mode = api.nvim_get_mode().mode
local is_line = mode == "V"
local is_block = mode == "\x16"
if not is_line and not is_block then
return
end
end

function M.notify_multi_cursor()
if not M.should_notify_multi_cursor then
return
api.nvim_feedkeys(api.nvim_replace_termcodes("<ESC>" .. (right and "a" or "i"), true, true, true), "n", true)

multi_cursor_task = function()
multi_cursor_task = nil
local ranges = {} ---@type lsp.Range[]
local start_pos = api.nvim_buf_get_mark(0, "<") ---@type number[]
local end_pos = api.nvim_buf_get_mark(0, ">") ---@type number[]
for row = start_pos[1], end_pos[1] do
local line = vim.fn.getline(row)
local width = api.nvim_strwidth(line)
if width == 0 and (skip_empty or is_block) then
else
local max_col = math.max(0, width - 1)
-- (row, col) is (1, 0)-indexed
local s_col, e_col
if is_line then
s_col = api.nvim_strwidth(line:match("^%s*") or "")
e_col = max_col
else
e_col = math.min(max_col, end_pos[2])
s_col = math.min(e_col, start_pos[2])
end
local range = vim.lsp.util.make_given_range_params({ row, s_col }, { row, e_col }, 0, "utf-16").range
if right then
range = { start = range["end"], ["end"] = range["end"] }
else
range = { start = range.start, ["end"] = range.start }
end
table.insert(ranges, range)
end
end
if #ranges > 0 then
vscode.action("start-multiple-cursors", { args = { ranges } })
end
end
M.should_notify_multi_cursor = nil
local startPos = vim.fn.getcharpos("'<")
local endPos = vim.fn.getcharpos("'>")
vim.fn.VSCodeExtensionNotify(
"visual-edit",
M.multi_cursor_append,
M.multi_cursor_visual_mode,
startPos[2],
endPos[2],
startPos[3],
endPos[3],
M.multi_cursor_skip_empty
)
end

function M.setup_multi_cursor(group)
vim.api.nvim_create_autocmd({ "InsertEnter" }, {
group = group,
callback = M.notify_multi_cursor,
callback = function()
if multi_cursor_task then
vim.schedule(multi_cursor_task)
end
end,
})

-- Multiple cursors support for visual line/block modes
vim.keymap.set("x", "ma", function()
M.prepare_multi_cursor(true, true)
start_multi_cursor(true, true)
end)
vim.keymap.set("x", "mi", function()
M.prepare_multi_cursor(false, true)
start_multi_cursor(false, true)
end)
vim.keymap.set("x", "mA", function()
M.prepare_multi_cursor(true, false)
start_multi_cursor(true, false)
end)
vim.keymap.set("x", "mI", function()
M.prepare_multi_cursor(false, false)
start_multi_cursor(false, false)
end)
end

Expand Down
11 changes: 10 additions & 1 deletion src/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NeovimClient } from "neovim";
import { VimValue } from "neovim/lib/types/VimValue";
import { ConfigurationTarget, Disposable, commands, window, workspace } from "vscode";
import { ConfigurationTarget, Disposable, Range, Selection, commands, window, workspace } from "vscode";

function getActionName(action: string) {
return `neovim:${action}`;
Expand Down Expand Up @@ -109,6 +109,15 @@ class ActionManager implements Disposable {
}
}
});
this.add("start-multiple-cursors", (ranges: Range[]) => {
if (!window.activeTextEditor || ranges.length === 0) return;
const doc = window.activeTextEditor.document;
window.activeTextEditor.selections = ranges.map((range) => {
const { start, end } = range;
range = doc.validateRange(new Range(start.line, start.character, end.line, end.character));
return new Selection(range.start, range.end);
});
});
}

private initHooks() {
Expand Down
33 changes: 0 additions & 33 deletions src/cursor_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export class CursorManager implements Disposable {
if (gridId) this.gridCursorUpdates.add(gridId);
}),
eventBus.on("range-command", this.handleRangeCommand, this),
eventBus.on("visual-edit", this.handleMultipleCursors, this),
);
}

Expand Down Expand Up @@ -496,38 +495,6 @@ export class CursorManager implements Disposable {
}
}

private async handleMultipleCursors(data: EventBusData<"visual-edit">): Promise<void> {
// eslint-disable-next-line prefer-const
let [append, visualMode, startLine, endLine, startCol, endCol, skipEmpty] = data;
const mode = new Mode(visualMode);
startLine--;
endLine--;

if (!window.activeTextEditor) return;
await this.waitForCursorUpdate(window.activeTextEditor);
this.wantInsertCursorUpdate = false;

logger.debug(
`Spawning multiple cursors from lines: [${startLine}, ${endLine}], col: [${startCol}, ${endCol}], mode: ${mode.visual}, append: ${append}, skipEmpty: ${skipEmpty}`,
);
const selections: Selection[] = [];
const doc = window.activeTextEditor.document;
for (let line = startLine; line <= endLine; line++) {
const lineDef = doc.lineAt(line);
// always skip empty lines for visual block mode
if (lineDef.text.trim() === "" && (skipEmpty || mode.visual === "block")) continue;
let char = 0;
if (mode.visual === "line") {
char = append ? lineDef.range.end.character : lineDef.firstNonWhitespaceCharacterIndex;
} else {
char = append ? Math.max(startCol, endCol) : Math.min(startCol, endCol) - 1;
}
logger.debug(`Multiple cursor at: [${line}, ${char}]`);
selections.push(new Selection(line, char, line, char));
}
window.activeTextEditor.selections = selections;
}

public dispose(): void {
this.disposables.forEach((d) => d.dispose());
}
Expand Down
1 change: 0 additions & 1 deletion src/eventBus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ type EventsMapping = {
];
["visual-changed"]: [number];
["range-command"]: any;
["visual-edit"]: [boolean, string, number, number, number, number, boolean];
};

export interface Event<T extends keyof EventsMapping = keyof EventsMapping> {
Expand Down

0 comments on commit f4a6ac5

Please sign in to comment.