Compare commits

..
10 Commits
Author SHA1 Message Date
dualfroz 7b615d9eda Describe this repository 2026-10-22 21:08:07 +02:00
dualfroz 111b528272 refactor(buffer): move the current window cursor lookup to utils 2026-10-03 18:54:01 +02:00
upstream 990e567f55 Upstream vscode-neovim/vscode-neovim state of the touched files before 3954b60dc5 2026-10-03 18:54:01 +02:00
dualfroz 955555921e fix(window): keep window commands typed after timeoutlen in vscode
Once 'timeoutlen' passes after a lone <C-w>, nvim runs its builtin window
command, which reads the next key without mappings, so the vscode window
mappings were skipped (e.g. <C-w>c closed the nvim window instead of the
editor).

Map a lone <C-w> to read the next key and feed the whole command again,
with its count, remapped when one of the window mappings handles it
(special keys like <C-w><C-Down> included).

Fixes #2466
2026-10-03 02:24:35 +02:00
upstream 17cff16264 Upstream vscode-neovim/vscode-neovim state of the touched files before 28225f71a4 2026-10-03 02:24:35 +02:00
dualfroz ea18b8e453 fix(buffer): keep the nvim cursor when nvim switches to another document
When nvim moves to a buffer whose document is not shown (e.g. a jump to a
global mark), vscode shows the document with the selection it saved last
time, and the layout sync then sends that selection back to nvim,
overwriting the position nvim jumped to.

Read the cursor of the current nvim window before showing the document
and apply it to the editor, so the layout sync sends nvim its own cursor.

Fixes #2699
2026-10-03 02:24:35 +02:00
upstream 6e63acc87e Upstream vscode-neovim/vscode-neovim state of the touched files before bdac2a3e1d 2026-10-03 02:24:35 +02:00
dualfrozandwongxy 5cc37c9f0a fix(cursor): keep the visual selection whole over characters outside the BMP (#2683)
* fix(cursor): keep the visual selection whole over characters outside the BMP

The inclusive Nvim end column was turned into an exclusive VSCode one by
adding a single UTF-16 unit. A character outside the BMP is two of them, so
the selection ended in the middle of a surrogate pair and drifted away from
where Nvim had the cursor: moving right over it took two presses and the
selected region no longer matched.

Step over the whole character instead, using its byte length.

Closes #2535

* test(cursor): add visual selection tests for non-BMP characters

* test(cursor): expect the astral pair to be whole in the mixed-character selection

---------

Co-authored-by: wongxy <xiyao.wong@foxmail.com>
2026-09-23 18:39:53 +08:00
upstream aacc554f30 Upstream vscode-neovim/vscode-neovim state of the touched files before 594cab76b1 2026-09-23 12:39:53 +02:00
dualfroz b87849804c test(cursor): expect the astral pair to be whole in the mixed-character selection 2026-09-23 06:25:40 +02:00
9 changed files with 2015 additions and 1 deletions

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
# Contributions to vscode-neovim/vscode-neovim
Vim mode for VSCode, powered by Neovim
This repository holds only my commits to the upstream project, with their original author, date and diff.
Each of my commits is preceded by a snapshot of the upstream files it touched, so every diff shows exactly what I changed.
## Merged pull requests
- [Keep visual selections whole over characters outside the BMP](https://github.com/vscode-neovim/vscode-neovim/pull/2683) (2026-09-23)
+8
View File
@@ -177,4 +177,12 @@ function M.win_set_option(win, name, value)
return M.set_option_value(name, value, { win = win }) return M.set_option_value(name, value, { win = win })
end end
---Get a buffer variable safely. Returns nil if the variable does not exist.
function M.buf_get_var(buf, name)
local ok, value = pcall(api.nvim_buf_get_var, buf, name)
if ok then
return value
end
end
return M return M
@@ -0,0 +1,169 @@
function! s:split(...) abort
let direction = a:1
let file = exists('a:2') ? a:2 : ''
call VSCodeCall(direction ==# 'h' ? 'workbench.action.splitEditorDown' : 'workbench.action.splitEditorRight')
if !empty(file)
call VSCodeExtensionNotify('open-file', expand(file), 'all')
endif
endfunction
function! s:splitNew(...)
let file = a:2
call s:split(a:1, empty(file) ? '__vscode_new__' : file)
endfunction
function! s:closeOtherEditors()
call VSCodeNotify('workbench.action.closeEditorsInOtherGroups')
call VSCodeNotify('workbench.action.closeOtherEditors')
endfunction
function! s:manageEditorHeight(...)
let count = a:1
let to = a:2
for i in range(1, count ? count : 1)
call VSCodeNotify(to ==# 'increase' ? 'workbench.action.increaseViewHeight' : 'workbench.action.decreaseViewHeight')
endfor
endfunction
function! s:manageEditorWidth(...)
let count = a:1
let to = a:2
for i in range(1, count ? count : 1)
call VSCodeNotify(to ==# 'increase' ? 'workbench.action.increaseViewWidth' : 'workbench.action.decreaseViewWidth')
endfor
endfunction
" A lone <C-w> runs the builtin window command once 'timeoutlen' passes, and that reads the next key
" without mappings, skipping the ones below. Read the key here instead and feed the whole command
" again, remapped when one of them handles it.
function! s:windowCommand(mode) abort
let keys = "\<C-w>" . getcharstr()
call feedkeys((v:count ? v:count : '') . keys, (empty(maparg(keytrans(keys), a:mode)) ? 'n' : 'm') . 'i')
endfunction
command! -complete=file -nargs=? Split call <SID>split('h', <q-args>)
command! -complete=file -nargs=? Vsplit call <SID>split('v', <q-args>)
command! -complete=file -nargs=? New call <SID>split('h', '__vscode_new__')
command! -complete=file -nargs=? Vnew call <SID>split('v', '__vscode_new__')
command! -bang Only if <q-bang> ==# '!' | call <SID>closeOtherEditors() | else | call VSCodeNotify('workbench.action.joinAllGroups') | endif
AlterCommand sp[lit] Split
AlterCommand vs[plit] Vsplit
AlterCommand new New
AlterCommand vne[w] Vnew
AlterCommand on[ly] Only
nnoremap <C-w> <Cmd>call <SID>windowCommand('n')<CR>
xnoremap <C-w> <Cmd>call <SID>windowCommand('x')<CR>
" buffer management
nnoremap <C-w>n <Cmd>call <SID>splitNew('h', '__vscode_new__')<CR>
xnoremap <C-w>n <Cmd>call <SID>splitNew('h', '__vscode_new__')<CR>
nnoremap <C-w>q <Cmd>call VSCodeNotify('workbench.action.closeActiveEditor')<CR>
xnoremap <C-w>q <Cmd>call VSCodeNotify('workbench.action.closeActiveEditor')<CR>
nnoremap <C-w>c <Cmd>call VSCodeNotify('workbench.action.closeActiveEditor')<CR>
xnoremap <C-w>c <Cmd>call VSCodeNotify('workbench.action.closeActiveEditor')<CR>
nnoremap <C-w><C-c> <Cmd>call VSCodeNotify('workbench.action.closeActiveEditor')<CR>
xnoremap <C-w><C-c> <Cmd>call VSCodeNotify('workbench.action.closeActiveEditor')<CR>
" window/splits management
nnoremap <C-w>s <Cmd>call <SID>split('h')<CR>
xnoremap <C-w>s <Cmd>call <SID>split('h')<CR>
nnoremap <C-w><C-s> <Cmd>call <SID>split('h')<CR>
xnoremap <C-w><C-s> <Cmd>call <SID>split('h')<CR>
nnoremap <C-w>v <Cmd>call <SID>split('v')<CR>
xnoremap <C-w>v <Cmd>call <SID>split('v')<CR>
nnoremap <C-w><C-v> <Cmd>call <SID>split('v')<CR>
xnoremap <C-w><C-v> <Cmd>call <SID>split('v')<CR>
nnoremap <C-w>= <Cmd>call VSCodeNotify('workbench.action.evenEditorWidths')<CR>
xnoremap <C-w>= <Cmd>call VSCodeNotify('workbench.action.evenEditorWidths')<CR>
nnoremap <C-w>_ <Cmd>call VSCodeNotify('workbench.action.toggleEditorWidths')<CR>
xnoremap <C-w>_ <Cmd>call VSCodeNotify('workbench.action.toggleEditorWidths')<CR>
nnoremap <C-w>+ <Cmd>call <SID>manageEditorHeight(v:count, 'increase')<CR>
xnoremap <C-w>+ <Cmd>call <SID>manageEditorHeight(v:count, 'increase')<CR>
nnoremap <C-w>- <Cmd>call <SID>manageEditorHeight(v:count, 'decrease')<CR>
xnoremap <C-w>- <Cmd>call <SID>manageEditorHeight(v:count, 'decrease')<CR>
nnoremap <C-w>> <Cmd>call <SID>manageEditorWidth(v:count, 'increase')<CR>
xnoremap <C-w>> <Cmd>call <SID>manageEditorWidth(v:count, 'increase')<CR>
nnoremap <C-w>< <Cmd>call <SID>manageEditorWidth(v:count, 'decrease')<CR>
xnoremap <C-w>< <Cmd>call <SID>manageEditorWidth(v:count, 'decrease')<CR>
nnoremap <C-w>o <Cmd>call VSCodeNotify('workbench.action.joinAllGroups')<CR>
xnoremap <C-w>o <Cmd>call VSCodeNotify('workbench.action.joinAllGroups')<CR>
nnoremap <C-w><C-o> <Cmd>call VSCodeNotify('workbench.action.joinAllGroups')<CR>
xnoremap <C-w><C-o> <Cmd>call VSCodeNotify('workbench.action.joinAllGroups')<CR>
" window navigation
nnoremap <C-w>j <Cmd>call VSCodeNotify('workbench.action.navigateDown')<CR>
xnoremap <C-w>j <Cmd>call VSCodeNotify('workbench.action.navigateDown')<CR>
nnoremap <C-w>k <Cmd>call VSCodeNotify('workbench.action.navigateUp')<CR>
xnoremap <C-w>k <Cmd>call VSCodeNotify('workbench.action.navigateUp')<CR>
nnoremap <C-w>h <Cmd>call VSCodeNotify('workbench.action.navigateLeft')<CR>
xnoremap <C-w>h <Cmd>call VSCodeNotify('workbench.action.navigateLeft')<CR>
nnoremap <C-w>l <Cmd>call VSCodeNotify('workbench.action.navigateRight')<CR>
xnoremap <C-w>l <Cmd>call VSCodeNotify('workbench.action.navigateRight')<CR>
nnoremap <C-w><Down> <Cmd>call VSCodeNotify('workbench.action.navigateDown')<CR>
xnoremap <C-w><Down> <Cmd>call VSCodeNotify('workbench.action.navigateDown')<CR>
nnoremap <C-w><Up> <Cmd>call VSCodeNotify('workbench.action.navigateUp')<CR>
xnoremap <C-w><Up> <Cmd>call VSCodeNotify('workbench.action.navigateUp')<CR>
nnoremap <C-w><Left> <Cmd>call VSCodeNotify('workbench.action.navigateLeft')<CR>
xnoremap <C-w><Left> <Cmd>call VSCodeNotify('workbench.action.navigateLeft')<CR>
nnoremap <C-w><Right> <Cmd>call VSCodeNotify('workbench.action.navigateRight')<CR>
xnoremap <C-w><Right> <Cmd>call VSCodeNotify('workbench.action.navigateRight')<CR>
nnoremap <C-w><C-j> <Cmd>call VSCodeNotify('workbench.action.moveEditorToBelowGroup')<CR>
xnoremap <C-w><C-j> <Cmd>call VSCodeNotify('workbench.action.moveEditorToBelowGroup')<CR>
nnoremap <C-w><C-k> <Cmd>call VSCodeNotify('workbench.action.moveEditorToAboveGroup')<CR>
xnoremap <C-w><C-k> <Cmd>call VSCodeNotify('workbench.action.moveEditorToAboveGroup')<CR>
nnoremap <C-w><C-h> <Cmd>call VSCodeNotify('workbench.action.moveEditorToLeftGroup')<CR>
xnoremap <C-w><C-h> <Cmd>call VSCodeNotify('workbench.action.moveEditorToLeftGroup')<CR>
nnoremap <C-w><C-l> <Cmd>call VSCodeNotify('workbench.action.moveEditorToRightGroup')<CR>
xnoremap <C-w><C-l> <Cmd>call VSCodeNotify('workbench.action.moveEditorToRightGroup')<CR>
nnoremap <C-w><C-Down> <Cmd>call VSCodeNotify('workbench.action.moveEditorToBelowGroup')<CR>
xnoremap <C-w><C-Down> <Cmd>call VSCodeNotify('workbench.action.moveEditorToBelowGroup')<CR>
nnoremap <C-w><C-Up> <Cmd>call VSCodeNotify('workbench.action.moveEditorToAboveGroup')<CR>
xnoremap <C-w><C-Up> <Cmd>call VSCodeNotify('workbench.action.moveEditorToAboveGroup')<CR>
nnoremap <C-w><C-Left> <Cmd>call VSCodeNotify('workbench.action.moveEditorToLeftGroup')<CR>
xnoremap <C-w><C-Left> <Cmd>call VSCodeNotify('workbench.action.moveEditorToLeftGroup')<CR>
nnoremap <C-w><C-Right> <Cmd>call VSCodeNotify('workbench.action.moveEditorToRightGroup')<CR>
xnoremap <C-w><C-Right> <Cmd>call VSCodeNotify('workbench.action.moveEditorToRightGroup')<CR>
nnoremap <C-w><S-j> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupDown')<CR>
xnoremap <C-w><S-j> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupDown')<CR>
nnoremap <C-w><S-k> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupUp')<CR>
xnoremap <C-w><S-k> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupUp')<CR>
nnoremap <C-w><S-h> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupLeft')<CR>
xnoremap <C-w><S-h> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupLeft')<CR>
nnoremap <C-w><S-l> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupRight')<CR>
xnoremap <C-w><S-l> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupRight')<CR>
nnoremap <C-w><S-Down> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupDown')<CR>
xnoremap <C-w><S-Down> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupDown')<CR>
nnoremap <C-w><S-Up> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupUp')<CR>
xnoremap <C-w><S-Up> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupUp')<CR>
nnoremap <C-w><S-Left> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupLeft')<CR>
xnoremap <C-w><S-Left> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupLeft')<CR>
nnoremap <C-w><S-Right> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupRight')<CR>
xnoremap <C-w><S-Right> <Cmd>call VSCodeNotify('workbench.action.moveActiveEditorGroupRight')<CR>
nnoremap <C-w>w <Cmd>call VSCodeNotify('workbench.action.focusNextGroup')<CR>
xnoremap <C-w>w <Cmd>call VSCodeNotify('workbench.action.focusNextGroup')<CR>
nnoremap <C-w><C-w> <Cmd>call VSCodeNotify('workbench.action.focusNextGroup')<CR>
xnoremap <C-w><C-w> <Cmd>call VSCodeNotify('workbench.action.focusNextGroup')<CR>
nnoremap <C-w>W <Cmd>call VSCodeNotify('workbench.action.focusPreviousGroup')<CR>
xnoremap <C-w>W <Cmd>call VSCodeNotify('workbench.action.focusPreviousGroup')<CR>
nnoremap <C-w>p <Cmd>call VSCodeNotify('workbench.action.focusPreviousGroup')<CR>
xnoremap <C-w>p <Cmd>call VSCodeNotify('workbench.action.focusPreviousGroup')<CR>
nnoremap <C-w>t <Cmd>call VSCodeNotify('workbench.action.focusFirstEditorGroup')<CR>
xnoremap <C-w>t <Cmd>call VSCodeNotify('workbench.action.focusFirstEditorGroup')<CR>
nnoremap <C-w>b <Cmd>call VSCodeNotify('workbench.action.focusLastEditorGroup')<CR>
xnoremap <C-w>b <Cmd>call VSCodeNotify('workbench.action.focusLastEditorGroup')<CR>
File diff suppressed because it is too large. Load diff
+109
View File
@@ -0,0 +1,109 @@
import { strict as assert } from "assert";
import path from "path";
import { NeovimClient } from "neovim";
import { Position, window } from "vscode";
import { wait, waitUntil } from "../../utils";
import {
attachTestNvimClient,
closeNvimClient,
closeAllActiveEditors,
sendVSCodeKeys,
assertContent,
sendNeovimKeys,
openTextDocument,
} from "./integrationUtils";
describe("Jumplist & jump actions & marks", () => {
// abc
let client: NeovimClient;
before(async () => {
client = await attachTestNvimClient();
});
after(async () => {
await closeNvimClient(client);
await closeAllActiveEditors();
});
it("Jump to definition to another file", async function () {
this.retries(3);
await openTextDocument(path.join(__dirname, "../../../test_fixtures/b.ts"));
await wait(2000);
await sendVSCodeKeys("jjjjjl");
await sendVSCodeKeys("gd", 2000);
await sendNeovimKeys(client, "<C-o>", 500);
await assertContent(
{
cursor: [5, 1],
},
client,
);
});
// currently too flaky
it.skip("Jump to definition in same file", async function () {
this.retries(3);
await openTextDocument(path.join(__dirname, "../../../test_fixtures/go-to-def-same-file.ts"));
await wait(2000);
await sendVSCodeKeys("gg049jgd", 2000);
await sendVSCodeKeys("jgd", 1000);
await assertContent(
{
cursor: [4, 9],
},
client,
);
await sendNeovimKeys(client, "<C-o>");
await assertContent(
{
cursor: [27, 9],
},
client,
);
await sendNeovimKeys(client, "<C-o>");
await assertContent(
{
cursor: [49, 0],
},
client,
);
});
it("lower-case marks should still exist after changes #543", async function () {
const editor = await openTextDocument({ content: ["abc", "def", "uvw", "xyz"].join("\n") });
await wait(300);
await sendNeovimKeys(client, "maG");
editor.edit((builder) => {
builder.replace(new Position(0, 0), "ABC");
});
await wait(300);
await sendNeovimKeys(client, "'a");
await wait(300);
await assertContent({ cursor: [0, 0] }, client, editor);
});
it("Global mark jump to another file keeps the mark position #2699", async () => {
const markEditor = await openTextDocument(path.join(__dirname, "../../../test_fixtures/scrolltest.txt"));
await sendVSCodeKeys("24G4lmA");
await assertContent({ cursor: [23, 4] }, client);
// Leave a different selection behind for VSCode to restore when the file is shown again
await sendVSCodeKeys("29G02l");
await assertContent({ cursor: [28, 2] }, client);
await openTextDocument(path.join(__dirname, "../../../test_fixtures/cursor-revealing.txt"));
await sendNeovimKeys(client, "`A");
await waitUntil(() => assert.equal(window.activeTextEditor?.document, markEditor.document));
// The restored selection would overwrite the mark position only after the editor is shown
await wait(1000);
await assertContent({ cursor: [23, 4] }, client);
});
});
+3 -1
View File
@@ -327,8 +327,10 @@ describe("Multi-width characters", () => {
await sendVSCodeKeys("v"); await sendVSCodeKeys("v");
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 1)] }, client); await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 1)] }, client);
// the cursor sits on the astral char, whose two UTF-16 units both belong to
// the selection: a, the modifier letter and the pair take offsets 0 to 4
await sendVSCodeKeys("ll"); await sendVSCodeKeys("ll");
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 3)] }, client); await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 4)] }, client);
await sendVSCodeKeys("l"); await sendVSCodeKeys("l");
await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 5)] }, client); await assertContent({ vsCodeSelections: [new vscode.Selection(0, 0, 0, 5)] }, client);
@@ -0,0 +1,655 @@
import path from "path";
import os from "os";
import fs from "fs";
import { strict as assert } from "assert";
import vscode, { Selection } from "vscode";
import { NeovimClient } from "neovim";
import { wait, waitUntil } from "../../utils";
import {
attachTestNvimClient,
assertContent,
setCursor,
sendVSCodeKeys,
closeAllActiveEditors,
sendEscapeKey,
closeNvimClient,
getVScodeCursor,
sendVSCodeKeysAtomic,
openTextDocument,
sendInsertKey,
sendVSCodeCommand,
sendNeovimKeys,
itSkipMacos,
} from "./integrationUtils";
describe("VSCode integration specific stuff", () => {
let client: NeovimClient;
before(async () => {
client = await attachTestNvimClient();
});
after(async () => {
await closeNvimClient(client);
await closeAllActiveEditors();
});
beforeEach(async () => {
await closeAllActiveEditors();
});
it("Doesnt move cursor on peek definition", async () => {
const doc = (
await openTextDocument({
content: 'declare function test(a: number): void;\n\ntest("")\n',
language: "typescript",
})
).document;
await setCursor(2, 1);
// peek definition opens another editor. make sure the cursor won't be leaked into primary editor
await vscode.commands.executeCommand("editor.action.peekDefinition", doc.uri, new vscode.Position(2, 1));
await wait(500);
await vscode.commands.executeCommand("closeReferenceSearch", doc.uri, new vscode.Position(2, 1));
await wait(500);
await assertContent(
{
cursor: [2, 1],
},
client,
);
});
it("Moves on cursor on go definition", async () => {
const doc = (
await openTextDocument({
content: 'declare function test(a: number): void;\n\ntest("")\n',
language: "typescript",
})
).document;
await setCursor(2, 1);
await vscode.commands.executeCommand("editor.action.goToTypeDefinition", doc.uri, new vscode.Position(2, 1));
await wait(1000);
await assertContent(
{
cursor: [0, 17],
},
client,
);
});
// TODO: always fails on CI, possible something with screen dimensions?
it.skip("Editor cursor revealing", async () => {
await openTextDocument(path.join(__dirname, "../../../test_fixtures/cursor-revealing.txt"));
await sendVSCodeKeys("90j");
await assertContent({ cursor: [90, 0], vsCodeVisibleRange: { bottom: 90 } }, client);
await sendVSCodeKeys("zt");
await assertContent({ cursor: [90, 0], vsCodeVisibleRange: { top: 90 } }, client);
// await sendVSCodeKeys("40k");
// await assertContent({ cursor: [90, 0], vsCodeVisibleRange: { bottom: 50 } }, client);
});
it("Scrolling actions", async () => {
const editor = await openTextDocument(path.join(__dirname, "../../../test_fixtures/scrolltest.txt"));
await sendVSCodeCommand("vscode-neovim.ctrl-f");
let visibleRange = editor.visibleRanges[0];
const topLine = visibleRange.start.line;
const bottomLine = visibleRange.end.line;
const cursorLine = editor.selection.active.line;
// Viewport height differs across VS Code versions / CI; cursor should stay within the visible slice.
assert.ok(
cursorLine >= topLine && cursorLine <= bottomLine,
`cursor ${cursorLine} not in visible range ${topLine}-${bottomLine}`,
);
await assertContent(
{
cursor: [cursorLine, 0],
},
client,
);
await sendVSCodeKeys("L", 400);
visibleRange = editor.visibleRanges[0];
const cursor = getVScodeCursor(editor);
assert.ok(cursor[0] <= visibleRange.end.line && cursor[0] >= visibleRange.end.line - 1);
await assertContent(
{
cursor,
},
client,
);
await sendVSCodeKeys("M", 400);
visibleRange = editor.visibleRanges[0];
await assertContent(
{
cursor: [editor.selection.active.line, 0],
},
client,
);
const middleline = visibleRange.start.line + (visibleRange.end.line - visibleRange.start.line) / 2;
assert.ok(editor.selection.active.line >= middleline - 1);
assert.ok(editor.selection.active.line <= middleline + 1);
await sendVSCodeKeys("H", 400);
visibleRange = editor.visibleRanges[0];
await assertContent(
{
cursor: [visibleRange.start.line, 0],
},
client,
);
});
// todo: sometimes it's failing, but most times works
it("Go to definition in other file - cursor is ok", async function () {
this.retries(3);
const doc2 = (await openTextDocument(path.join(__dirname, "../../../test_fixtures/b.ts"))).document;
await setCursor(3, 1);
await vscode.commands.executeCommand("editor.action.goToTypeDefinition", doc2.uri, new vscode.Position(2, 1));
await wait(500);
await assertContent(
{
cursor: [4, 16],
content: [
'export const a = "blah";',
"",
'export const b = "blah";',
"",
"export function someFunc(): void;",
"",
],
},
client,
);
});
itSkipMacos("Current mode is canceled when switching between editor panes", async () => {
await openTextDocument({
content: "blah1",
});
await wait(500);
const doc2 = await vscode.workspace.openTextDocument({
content: "blah2",
});
await vscode.window.showTextDocument(doc2, vscode.ViewColumn.Two);
await wait(500);
await client.command("au BufEnter * stopinsert");
await wait(500);
await sendVSCodeCommand("workbench.action.focusSecondEditorGroup", "", 1000);
await sendInsertKey("I");
await assertContent(
{
content: ["blah2"],
cursorStyle: "line",
mode: "i",
},
client,
);
// make sure the changes will be synced with neovim
await sendVSCodeKeys("test", 1000);
await sendVSCodeCommand("workbench.action.focusFirstEditorGroup", "", 2000);
await assertContent(
{
content: ["blah1"],
cursorStyle: "block",
mode: "n",
},
client,
);
await sendVSCodeKeys("V");
await assertContent(
{
content: ["blah1"],
mode: "V",
},
client,
);
await sendVSCodeCommand("workbench.action.focusSecondEditorGroup", "", 2000);
await assertContent(
{
content: ["testblah2"],
mode: "n",
},
client,
);
});
it("Current mode is canceled when switching between editor tabs", async () => {
const doc1 = await vscode.workspace.openTextDocument({
content: "blah1",
});
await vscode.window.showTextDocument(doc1, vscode.ViewColumn.One);
const doc2 = await vscode.workspace.openTextDocument({
content: "blah2",
});
await vscode.window.showTextDocument(doc2, vscode.ViewColumn.One);
await wait(500);
await client.command("au BufEnter * stopinsert");
await wait(500);
await sendInsertKey("I");
await assertContent(
{
content: ["blah2"],
cursorStyle: "line",
mode: "i",
},
client,
);
// make sure the changes will be synced with neovim
await sendVSCodeKeys("test", 500);
await sendVSCodeCommand("workbench.action.previousEditorInGroup", "", 500);
await assertContent(
{
content: ["blah1"],
cursorStyle: "block",
mode: "n",
},
client,
);
await sendVSCodeKeys("V");
await assertContent(
{
content: ["blah1"],
mode: "V",
},
client,
);
await sendVSCodeCommand("workbench.action.nextEditorInGroup", "", 500);
await assertContent(
{
content: ["testblah2"],
cursorStyle: "block",
mode: "n",
},
client,
);
});
it("Cursor is ok when go to def into editor in the other pane", async () => {
const doc1 = await vscode.workspace.openTextDocument(path.join(__dirname, "../../../test_fixtures/bb.ts"));
await vscode.window.showTextDocument(doc1, vscode.ViewColumn.One);
await wait(1500);
const doc2 = await vscode.workspace.openTextDocument(
path.join(__dirname, "../../../test_fixtures/def-with-scroll.ts"),
);
await vscode.window.showTextDocument(doc2, vscode.ViewColumn.Two, true);
await wait(1500);
// make sure we're in first editor group
await vscode.commands.executeCommand("workbench.action.focusFirstEditorGroup");
await wait(400);
await sendVSCodeKeys("gg5j", 0);
await wait(1000);
await vscode.commands.executeCommand(
"editor.action.revealDefinitionAside",
doc1.uri,
new vscode.Position(5, 1),
);
await wait(1500);
await assertContent(
{
cursor: [115, 16],
},
client,
);
});
// !Passes only when the runner is in foreground
itSkipMacos("Cursor is preserved if same doc is opened in two editor columns", async () => {
const doc = (
await openTextDocument(path.join(__dirname, "../../../test_fixtures/cursor-preserved-between-columns.txt"))
).document;
await wait(1000);
await sendVSCodeKeys("gg050j", 1000);
await vscode.window.showTextDocument(doc, vscode.ViewColumn.Two, false);
await wait(1000);
await sendVSCodeKeys("gg0100j", 1000);
await sendVSCodeCommand("workbench.action.focusFirstEditorGroup", "", 2000);
await sendVSCodeKeys("l");
await assertContent(
{
cursor: [50, 1],
},
client,
);
await sendVSCodeCommand("workbench.action.focusSecondEditorGroup", "", 1000);
await sendVSCodeKeys("l");
await assertContent(
{
cursor: [100, 1],
},
client,
);
});
it("Window commands typed after timeoutlen still go to vscode #2466", async () => {
await openTextDocument({ content: "blah" });
const timeoutlen = (await client.getOption("timeoutlen")) as number;
await client.setOption("timeoutlen", 100);
try {
await sendNeovimKeys(client, "<C-w>", 500);
await sendNeovimKeys(client, "v");
await waitUntil(() =>
assert.equal(vscode.window.tabGroups.all.length, 2, "<C-w>v should split the editor"),
);
} finally {
await client.setOption("timeoutlen", timeoutlen);
await vscode.commands.executeCommand("workbench.action.joinAllGroups");
}
});
it("Opens a file through e command", async () => {
const filePath = path.join(os.tmpdir(), Math.random().toString());
fs.writeFileSync(filePath, ["line 1"].join("\n"), {
encoding: "utf8",
});
await openTextDocument({ content: "blah" });
await sendVSCodeKeysAtomic(":e " + filePath, 500);
await sendVSCodeCommand("vscode-neovim.commit-cmdline", "", 500);
await assertContent(
{
content: ["line 1"],
},
client,
);
});
it("Spawning command line from visual line mode produces vscode selection", async () => {
await openTextDocument({ content: ["a1", "b1", "c1"].join("\n") });
await sendVSCodeKeys("Vj");
await sendVSCodeCommand("workbench.action.quickOpen");
await assertContent(
{
vsCodeSelections: [new vscode.Selection(0, 0, 1, 2)],
},
client,
);
await vscode.commands.executeCommand("workbench.action.closeQuickOpen");
await sendEscapeKey();
await sendVSCodeKeys("GVk");
await sendVSCodeCommand("workbench.action.quickOpen");
await assertContent(
{
vsCodeSelections: [new vscode.Selection(2, 2, 1, 0)],
},
client,
);
await vscode.commands.executeCommand("workbench.action.closeQuickOpen");
});
it("Spawning command line from visual mode produces vscode selection", async () => {
const documentContent = "Hello World!";
await openTextDocument({ content: documentContent });
await sendVSCodeKeys("v$");
await sendVSCodeCommand("workbench.action.quickOpen");
await assertContent(
{
vsCodeSelections: [new vscode.Selection(0, 0, 0, documentContent.length)],
},
client,
);
await vscode.commands.executeCommand("workbench.action.closeQuickOpen");
await sendEscapeKey();
await sendVSCodeKeys("gvo");
await sendVSCodeCommand("vscode-neovim.send", "<C-P>");
await assertContent(
{
vsCodeSelections: [new vscode.Selection(0, documentContent.length, 0, 0)],
},
client,
);
await vscode.commands.executeCommand("workbench.action.closeQuickOpen");
});
it("Filetype detection", async function () {
this.retries(3);
const doc1 = await vscode.workspace.openTextDocument(path.join(__dirname, "../../../test_fixtures/bb.ts"));
await vscode.window.showTextDocument(doc1, vscode.ViewColumn.One);
await wait(1500);
const buf = await client.buffer;
const type = await client.request("nvim_buf_get_option", [buf.id, "filetype"]);
assert.strictEqual("typescript", type);
});
it("Filetype detection (jupyter notebook)", async function () {
this.retries(2);
const note = await vscode.workspace.openNotebookDocument(
vscode.Uri.file(path.join(__dirname, "../../../test_fixtures/window-changed.ipynb")),
);
await vscode.window.showNotebookDocument(note, { viewColumn: vscode.ViewColumn.One });
await wait(1000);
const buf = await client.buffer;
const type = await client.request("nvim_buf_get_option", [buf.id, "filetype"]);
assert.strictEqual("python", type);
});
it("Next search result works", async () => {
await openTextDocument(path.join(__dirname, "../../../test_fixtures/incsearch-scroll.ts"));
await sendVSCodeCommand("workbench.action.findInFiles", { query: "blah" }, 500);
await sendVSCodeCommand("search.action.refreshSearchResults");
await sendVSCodeCommand("workbench.action.focusFirstEditorGroup");
await sendVSCodeCommand("search.action.focusNextSearchResult");
await assertContent(
{
vsCodeSelections: [new Selection(115, 16, 115, 20)],
neovimCursor: [115, 19],
},
client,
);
await sendVSCodeCommand("search.action.focusNextSearchResult");
await assertContent(
{
vsCodeSelections: [new Selection(170, 16, 170, 20)],
neovimCursor: [170, 19],
},
client,
);
});
it("Edit on long lines works", async () => {
await openTextDocument(path.join(__dirname, "../../../test_fixtures/long-line.txt"));
await sendVSCodeKeys("gg^");
await sendVSCodeKeys("2jb");
await assertContent(
{
cursor: [1, 1580],
},
client,
);
await sendVSCodeKeys("iabc");
await sendEscapeKey();
await assertContent(
{
cursor: [1, 1582],
},
client,
);
await sendVSCodeKeys("jhk");
await assertContent(
{
cursor: [1, 10],
},
client,
);
await sendVSCodeKeys("Aabc");
await sendEscapeKey();
await assertContent(
{
cursor: [1, 1589],
},
client,
);
await sendVSCodeKeys("Iabc");
await sendEscapeKey();
await assertContent(
{
cursor: [1, 2],
},
client,
);
await sendVSCodeKeys("$");
await assertContent(
{
cursor: [1, 1592],
},
client,
);
});
it("cursorMove with wrappedLine should works #1498", async () => {
await openTextDocument({ content: " hello\n\nworld" });
await wait(200);
await sendNeovimKeys(client, "ll", 200);
vscode.commands.executeCommand("cursorMove", { to: "down", by: "wrappedLine" });
await wait(200);
vscode.commands.executeCommand("cursorMove", { to: "down", by: "wrappedLine" });
await wait(200);
await assertContent({ cursor: [2, 2] }, client);
});
it("vim.ui.select works", async () => {
await client.lua(`
vim.ui.select({ 'red', 'green', 'blue' }, {}, function(item, idx)
vim.g._ret_item = item
vim.g._ret_idx = idx
end)
`);
// wait enough time for it to open
await wait(100);
// select the last item
await vscode.commands.executeCommand("workbench.action.quickOpenNavigateNext");
await vscode.commands.executeCommand("workbench.action.quickOpenNavigateNext");
await vscode.commands.executeCommand("workbench.action.acceptSelectedQuickOpenItem");
// check the results
const actual_item = await client.getVar("_ret_item");
const actual_idx = await client.getVar("_ret_idx");
assert.strictEqual(actual_item, "blue");
assert.strictEqual(actual_idx, 3);
});
it("vim.ui.select with format_item works", async () => {
await client.lua(`
vim.ui.select({{ label = 'apple', detail = 'red' }, { label = 'avocado', detail = 'green' }}, {
format_item = function(item)
return item.label
end
}, function(item, idx)
vim.g._ret_item = item
vim.g._ret_idx = idx
end)
`);
// wait enough time for it to open
await wait(100);
// select the first item
await vscode.commands.executeCommand("workbench.action.acceptSelectedQuickOpenItem");
// check the results
const actual_item = (await client.getVar("_ret_item")) as { label: string; detail: string };
const actual_idx = await client.getVar("_ret_idx");
assert.ok(typeof actual_item === "object");
assert.strictEqual(actual_item.label, "apple");
assert.strictEqual(actual_item.detail, "red");
assert.strictEqual(actual_idx, 1);
});
it("vim.ui.select cancel returns nil", async () => {
await client.lua(`
vim.ui.select({ 'red', 'green', 'blue' }, {}, function(item, idx)
vim.g._ret_item = item
vim.g._ret_idx = idx
end)
`);
// wait enough time for it to open
await wait(100);
// cancel the dialog
await vscode.commands.executeCommand("workbench.action.closeQuickOpen");
// check the results
const actual_item = await client.getVar("_ret_item");
const actual_idx = await client.getVar("_ret_idx");
assert.strictEqual(actual_item, null);
assert.strictEqual(actual_idx, null);
});
it("vim.ui.input works", async () => {
await client.lua(`
vim.ui.input({ prompt = 'Enter a value: ', default = 'test' }, function(input)
vim.g._res = input
end)
`);
// wait enough time for it to open
await wait(100);
// confirm the value
await vscode.commands.executeCommand("workbench.action.acceptSelectedQuickOpenItem");
// check the results
const actual_input = await client.getVar("_res");
assert.strictEqual(actual_input, "test");
});
it("vim.ui.input empty not nil", async () => {
await client.lua(`
vim.ui.input({ prompt = 'Enter a value: ' }, function(input)
vim.g._res = input
end)
`);
// wait enough time for it to open
await wait(100);
// confirm the value
await vscode.commands.executeCommand("workbench.action.acceptSelectedQuickOpenItem");
// check the results
const actual_input = await client.getVar("_res");
assert.strictEqual(actual_input, "");
});
it("vim.ui.input cancel returns nil", async () => {
await client.lua(`
vim.ui.input({ prompt = 'Enter a value: ' }, function(input)
vim.g._res = input
end)
`);
// wait enough time for it to open
await wait(100);
// cancel the dialog
await vscode.commands.executeCommand("workbench.action.closeQuickOpen");
// check the results
const actual_input = await client.getVar("_res");
assert.strictEqual(actual_input, null);
});
});
+6
View File
@@ -0,0 +1,6 @@
export * from "./async";
export * from "./fs";
export * from "./neovim";
export * from "./pending_updates";
export * from "./text";
export * from "./vscode";
+18
View File
@@ -0,0 +1,18 @@
import { NeovimClient } from "neovim";
import { Position } from "vscode";
/**
* Get the (0, 0)-indexed byte cursor of the current neovim window, if it shows the given buffer
*/
export async function getCurrentWinCursorInBuf(client: NeovimClient, bufId: number): Promise<Position | undefined> {
const cursor = (await client.lua(
`
local buf = ...
if vim.api.nvim_get_current_buf() == buf then
return vim.api.nvim_win_get_cursor(0)
end
`,
[bufId],
)) as [number, number] | null;
return cursor ? new Position(cursor[0] - 1, cursor[1]) : undefined;
}