diff --git a/src/buffer_manager.ts b/src/buffer_manager.ts index c0e49bc..07b0f84 100644 --- a/src/buffer_manager.ts +++ b/src/buffer_manager.ts @@ -10,6 +10,7 @@ import { EventEmitter, LogLevel, NotebookDocument, + Position, ProgressLocation, Selection, TextDocument, @@ -30,7 +31,15 @@ import { config } from "./config"; import { EventBusData, eventBus } from "./eventBus"; import { createLogger } from "./logger"; import { MainController } from "./main_controller"; -import { ManualPromise, Progress, convertByteNumToCharNum, disposeAll, fileExists, wait } from "./utils"; +import { + ManualPromise, + Progress, + convertByteNumToCharNum, + convertVimPositionToEditorPosition, + disposeAll, + fileExists, + wait, +} from "./utils"; // NOTE: document and editors in vscode events and namespace are reference stable // Integration notes: @@ -348,6 +357,9 @@ export class BufferManager implements Disposable { this.textDocumentToBufferId.set(doc, bufnr); } if (window.activeTextEditor?.document !== doc) { + // Neovim moved to this buffer itself (e.g. a jump to a global mark), so its cursor + // must win over the selection vscode restores when showing the document again + const neovimCursor = await this.getNeovimCursorInBuffer(bufnr); const editor = await window.showTextDocument(doc, { // viewColumn: vscode.ViewColumn.Active, // !need to force editor to appear in the same column even if vscode 'revealIfOpen' setting is true @@ -355,6 +367,11 @@ export class BufferManager implements Disposable { preserveFocus: false, preview: false, }); + if (neovimCursor) { + const active = convertVimPositionToEditorPosition(editor, neovimCursor); + editor.selections = [new Selection(active, active)]; + editor.revealRange(editor.selection, TextEditorRevealType.InCenterIfOutsideViewport); + } // force resync this.onDidChangeEditorOptions(editor); } @@ -363,6 +380,22 @@ export class BufferManager implements Disposable { } } + /** + * Get the (0, 0)-indexed byte cursor of the current neovim window, if it shows the given buffer + */ + private async getNeovimCursorInBuffer(bufId: number): Promise { + const cursor = (await this.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; + } + private handleWindowChanged = async (winId: number, token: CancellationToken): Promise => { logger.debug(`window changed, target window id: ${winId}`); if (winId === 1000) { diff --git a/src/test/integ/jumping.test.ts b/src/test/integ/jumping.test.ts index a01ff38..c15dd6e 100644 --- a/src/test/integ/jumping.test.ts +++ b/src/test/integ/jumping.test.ts @@ -1,9 +1,10 @@ +import { strict as assert } from "assert"; import path from "path"; import { NeovimClient } from "neovim"; -import { Position } from "vscode"; +import { Position, window } from "vscode"; -import { wait } from "../../utils"; +import { wait, waitUntil } from "../../utils"; import { attachTestNvimClient, @@ -88,4 +89,21 @@ describe("Jumplist & jump actions & marks", () => { 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); + }); });