diff --git a/browser/src/Editor/NeovimEditor/NeovimEditorCommands.ts b/browser/src/Editor/NeovimEditor/NeovimEditorCommands.ts index b7ccce08a0..75e3eca621 100644 --- a/browser/src/Editor/NeovimEditor/NeovimEditorCommands.ts +++ b/browser/src/Editor/NeovimEditor/NeovimEditorCommands.ts @@ -12,6 +12,7 @@ import * as Oni from "oni-api" import { NeovimInstance } from "./../../neovim" import { CallbackCommand, CommandManager } from "./../../Services/CommandManager" import { ContextMenuManager } from "./../../Services/ContextMenu" +import { editorManager } from "./../../Services/EditorManager" import { findAllReferences, format, LanguageEditorIntegration } from "./../../Services/Language" import { replaceAll } from "./../../Utility" @@ -67,13 +68,22 @@ export class NeovimEditorCommands { const pasteContents = async (neovimInstance: NeovimInstance) => { const textToPaste = clipboard.readText() - const sanitizedText = replaceAll(textToPaste, { "<": "" }) - .split(os.EOL) - .join("") + const replacements = { "'": "''" } + replacements[os.EOL] = "\n" + const sanitizedTextLines = replaceAll(textToPaste, replacements) + await neovimInstance.command('let b:oniclipboard=@"') + await neovimInstance.command(`let @"='${sanitizedTextLines}'`) + + if (editorManager.activeEditor.mode === "insert") { + await neovimInstance.command("set paste") + await neovimInstance.input('"') + await neovimInstance.command("set nopaste") + } else { + await neovimInstance.command("normal! p") + } - await neovimInstance.command("set paste") - await neovimInstance.input(sanitizedText) - await neovimInstance.command("set nopaste") + await neovimInstance.command('let @"=b:oniclipboard') + await neovimInstance.command("unlet b:oniclipboard") } const commands = [ @@ -116,7 +126,13 @@ export class NeovimEditorCommands { "editor.clipboard.yank", "Clipboard: Yank", "Yank contents to clipboard", - () => this._neovimInstance.input("y"), + () => this._neovimInstance.command('normal! "+y'), + ), + new CallbackCommand( + "editor.clipboard.cut", + "Clipboard: Cut", + "Cut contents to clipboard", + () => this._neovimInstance.command('normal! "+x'), ), new CallbackCommand("oni.editor.findAllReferences", null, null, () => findAllReferences(), diff --git a/main/src/menu.ts b/main/src/menu.ts index 24687efe60..1f71746680 100644 --- a/main/src/menu.ts +++ b/main/src/menu.ts @@ -288,19 +288,19 @@ export const buildMenu = (mainWindow, loadInit) => { { label: "Copy", click(item, focusedWindow) { - executeVimCommand(focusedWindow, '\\"+y') + executeOniCommand(focusedWindow, "editor.clipboard.yank") }, }, { label: "Cut", click(item, focusedWindow) { - executeVimCommand(focusedWindow, '\\"+x') + executeOniCommand(focusedWindow, "editor.clipboard.cut") }, }, { label: "Paste", click(item, focusedWindow) { - executeVimCommand(focusedWindow, '\\"+gP') + executeOniCommand(focusedWindow, "editor.clipboard.paste") }, }, { diff --git a/test/CiTests.ts b/test/CiTests.ts index 9ee48869ae..4c2904646e 100644 --- a/test/CiTests.ts +++ b/test/CiTests.ts @@ -43,6 +43,7 @@ const CiTests = [ "Neovim.CallOniCommands", "NoInstalledNeovim", "Sidebar.ToggleSplitTest", + "LargePasteTest", "Snippets.BasicInsertTest", diff --git a/test/ci/LargePasteTest.ts b/test/ci/LargePasteTest.ts new file mode 100644 index 0000000000..7d3926e67d --- /dev/null +++ b/test/ci/LargePasteTest.ts @@ -0,0 +1,97 @@ +/** + * Test for pasting a large number of lines + * + * Regression test for #2414 + */ +import * as assert from "assert" +import * as Oni from "oni-api" + +import { createNewFile, getTemporaryFilePath, navigateToFile } from "./Common" + +export const test = async (oni: Oni.Plugin.Api) => { + const filePath = createLargeTestFile() + await oni.automation.waitForEditors() + + // open file with 2000 lines + navigateToFile(filePath, oni) + + // select everything + oni.automation.sendKeys("") + await oni.automation.waitFor(() => oni.editors.activeEditor.mode === "visual") + oni.automation.sendKeys("G") + await oni.automation.waitFor(() => oni.editors.activeEditor.activeBuffer.cursor.line === 1999) + + // copy and paste should result in 4000 lines + await copy(oni) + oni.automation.sendKeys("o") + await oni.automation.waitFor(() => oni.editors.activeEditor.mode === "insert") + await paste(oni, () => oni.editors.activeEditor.activeBuffer.lineCount === 4001) + assert.strictEqual(oni.editors.activeEditor.activeBuffer.lineCount, 4001) + + // go to first line, and copy first word ('this') + oni.automation.sendKeys("gg") + await oni.automation.waitFor(() => oni.editors.activeEditor.activeBuffer.cursor.line === 0) + oni.automation.sendKeys("v") + await oni.automation.waitFor(() => oni.editors.activeEditor.mode === "visual") + oni.automation.sendKeys("e") + await oni.automation.waitFor(() => oni.editors.activeEditor.activeBuffer.cursor.column === 3) + await copy(oni) + + // paste in the middle of the first word + oni.automation.sendKeys("3l") + await oni.automation.waitFor(() => oni.editors.activeEditor.activeBuffer.cursor.column === 3) + oni.automation.sendKeys("i") + await oni.automation.waitFor(() => oni.editors.activeEditor.mode === "insert") + await paste(oni, () => oni.editors.activeEditor.activeBuffer.cursor.column === 7) + + const [firstLine] = await oni.editors.activeEditor.activeBuffer.getLines(0, 1) + assert.strictEqual( + firstLine, + "thithiss is a line of 'text' that will be repeated a bunch of times to make for a large wall of 'text' to paste", + ) +} + +import * as fs from "fs" +import * as os from "os" + +const createLargeTestFile = (): string => { + const filePath = getTemporaryFilePath("js") + const line = + "this is a line of 'text' that will be repeated a bunch of times to make for a large wall of 'text' to paste" + + const lines = [] + for (let i = 0; i < 2000; i++) { + lines.push(line) + } + + fs.writeFileSync(filePath, lines.join(os.EOL)) + return filePath +} + +import { isMac } from "../../browser/src/Platform" + +const copy = async (oni: Oni.Plugin.Api) => { + if (isMac()) { + oni.automation.sendKeys("") + } else { + oni.automation.sendKeys("") + } + + await oni.automation.waitFor(() => oni.editors.activeEditor.mode === "normal") +} + +const paste = async ( + oni: Oni.Plugin.Api, + waitConditionChecker: Oni.Automation.WaitConditionChecker, +) => { + if (isMac()) { + oni.automation.sendKeys("") + } else { + oni.automation.sendKeys("") + } + + await oni.automation.waitFor(waitConditionChecker) + + oni.automation.sendKeys("") + await oni.automation.waitFor(() => oni.editors.activeEditor.mode === "normal") +}