diff --git a/package.json b/package.json index e0bcd16..8460eca 100644 --- a/package.json +++ b/package.json @@ -340,6 +340,14 @@ "key": "ctrl+x r", "command": "emacs.C-x_r", "when": "editorTextFocus" + },{ + "key": "alt+y", + "command": "emacs.M-y", + "when": "editorTextFocus" + },{ + "key": "backspace", + "command": "emacs.backspace", + "when": "editorTextFocus" } ] }, diff --git a/src/editor.ts b/src/editor.ts index 7b2043a..149eb1d 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -11,18 +11,20 @@ enum KeybindProgressMode { }; export class Editor { - private killRing: string; - private isKillRepeated: boolean; + private killRing: string[]; + private ringIndex: number; private keybindProgressMode: KeybindProgressMode; private registersStorage: { [key:string] : RegisterContent; }; + private yanked: boolean; constructor() { - this.killRing = ''; - this.isKillRepeated = false; + this.killRing = []; this.keybindProgressMode = KeybindProgressMode.None; this.registersStorage = {}; + this.ringIndex = 0; + this.yanked = false; vscode.window.onDidChangeTextEditorSelection(() => { - this.isKillRepeated = false; + this.yanked = false; }); } @@ -55,8 +57,7 @@ export class Editor { /** Behave like Emacs kill command */ kill(): void { - let saveIsKillRepeated = this.isKillRepeated, - promises = [ + let promises = [ vscode.commands.executeCommand("emacs.exitMarkMode"), vscode.commands.executeCommand("cursorEndSelect") ]; @@ -64,48 +65,38 @@ export class Editor { Promise.all(promises).then(() => { let selection = this.getSelection(), range = new vscode.Range(selection.start, selection.end); - this.setSelection(range.start, range.start); - this.isKillRepeated = saveIsKillRepeated; if (range.isEmpty) { - this.killEndOfLine(saveIsKillRepeated, range); + this.killEndOfLine(range); } else { this.killText(range); } }); } - private killEndOfLine(saveIsKillRepeated: boolean, range: vscode.Range): void { + private killEndOfLine(range: vscode.Range): void { let doc = vscode.window.activeTextEditor.document, eof = doc.lineAt(doc.lineCount - 1).range.end; - if (doc.lineCount && !range.end.isEqual(eof) && doc.lineAt(range.start.line).rangeIncludingLineBreak) { - this.isKillRepeated ? this.killRing += '\n' : this.killRing = '\n'; - saveIsKillRepeated = true; + this.killRing[this.ringIndex++] = vscode.window.activeTextEditor.document.getText(range); } else { this.setStatusBarMessage("End of buffer"); } - vscode.commands.executeCommand("deleteRight").then(() => { - this.isKillRepeated = saveIsKillRepeated; - }); + vscode.commands.executeCommand("deleteRight"); } private killText(range: vscode.Range): void { - let text = vscode.window.activeTextEditor.document.getText(range), - promises = [ - Editor.delete(range), - vscode.commands.executeCommand("emacs.exitMarkMode") - ]; - - this.isKillRepeated ? this.killRing += text : this.killRing = text; - Promise.all(promises).then(() => { - this.isKillRepeated = true; - }); + let text = vscode.window.activeTextEditor.document.getText(range); + this.killRing[this.ringIndex++] = text; + //max 20 items in the ring to avoid excessice memory use + //adjust if desired + this.ringIndex = this.ringIndex % 20; + Editor.delete(range); + vscode.commands.executeCommand("emacs.exitMarkMode"); } copy(range: vscode.Range = null): boolean { - this.killRing = ''; if (range === null) { range = this.getSelectionRange(); if (range === null) { @@ -113,7 +104,8 @@ export class Editor { return false; } } - this.killRing = vscode.window.activeTextEditor.document.getText(range); + this.killRing[this.ringIndex++] = vscode.window.activeTextEditor.document.getText(range); + this.ringIndex = this.ringIndex % 20; vscode.commands.executeCommand("emacs.exitMarkMode"); return this.killRing !== undefined; } @@ -128,14 +120,40 @@ export class Editor { return true; } - yank(): boolean { + async yank(): Promise { if (this.killRing.length === 0) { return false; } - vscode.window.activeTextEditor.edit(editBuilder => { - editBuilder.insert(this.getSelection().active, this.killRing); + await vscode.window.activeTextEditor.edit(editBuilder => { + editBuilder.insert(this.getSelection().active, this.killRing[this.ringIndex - 1]); + }); + this.yanked = true; + return true; + } + async yankPop(): Promise { + if(!this.yanked) { + return new Promise((resolve) => { + resolve(false); + }); + } + let currentPosition = this.getSelection().active; + let lines = this.killRing[this.ringIndex -1].split("\n"); + let linesNumber = lines.length - 1; + let lastLine = lines[linesNumber]; + let endPosition = currentPosition.translate(-linesNumber, -lastLine.length); + let deleteThis = new vscode.Range(currentPosition, endPosition); + await vscode.window.activeTextEditor.edit(editBuilder => { + editBuilder.delete(deleteThis); + }); + this.ringIndex--; + if (this.ringIndex === 0) { + this.ringIndex = this.killRing.length; + } + await vscode.window.activeTextEditor.edit(editBuilder => { + editBuilder.replace(this.getSelection().active, this.killRing[this.ringIndex - 1]); }); - this.isKillRepeated = false; + await vscode.commands.executeCommand("cancelSelection"); + this.yanked = true; return true; } @@ -297,11 +315,11 @@ export class Editor { text: text }); } - return; + return; } SaveTextToRegister(registerName: string): void { - if (null == registerName) { + if (null === registerName) { return; } let range : vscode.Range = this.getSelectionRange(); @@ -317,7 +335,7 @@ export class Editor { RestoreTextFromRegister(registerName: string): void { vscode.commands.executeCommand("emacs.exitMarkMode"); // emulate Emacs let obj : RegisterContent = this.registersStorage[registerName]; - if (null == obj) { + if (null === obj) { this.setStatusBarMessage("Register does not contain text."); return; } @@ -331,4 +349,4 @@ export class Editor { } return; } -} +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index a38dc5b..8f2a8be 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,7 +9,7 @@ export function activate(context: vscode.ExtensionContext): void { // Edit "C-k", "C-w", "M-w", "C-y", "C-x_C-o", - "C-x_u", "C-/", + "C-x_u", "C-/", "M-y", "backspace", // R-Mode "C-x_r" @@ -35,9 +35,8 @@ export function activate(context: vscode.ExtensionContext): void { element ); }) - ) + ); }); - // 'type' is not an "emacs." command and should be registered separately context.subscriptions.push(vscode.commands.registerCommand("type", function (args) { if (!vscode.window.activeTextEditor) { @@ -45,7 +44,6 @@ export function activate(context: vscode.ExtensionContext): void { } op.onType(args.text); })); - initMarkMode(context); } @@ -55,9 +53,13 @@ export function deactivate(): void { function initMarkMode(context: vscode.ExtensionContext): void { context.subscriptions.push(vscode.commands.registerCommand( 'emacs.enterMarkMode', () => { - initSelection(); - inMarkMode = true; - vscode.window.setStatusBarMessage("Mark Set", 1000); + if(!inMarkMode) { + initSelection(); + inMarkMode = true; + vscode.window.setStatusBarMessage("Mark Set", 1000); + } else { + vscode.commands.executeCommand("emacs.exitMarkMode"); + } }) ); diff --git a/src/operation.ts b/src/operation.ts index 46c5f8f..923b26c 100644 --- a/src/operation.ts +++ b/src/operation.ts @@ -1,5 +1,5 @@ import {Editor} from './editor'; - +import {commands} from 'vscode'; export class Operation { private editor: Editor; private commandList: { [key: string]: (...args: any[]) => any, thisArgs?: any } = {}; @@ -48,6 +48,23 @@ export class Operation { "C-x_r": () => { this.editor.setRMode(); }, + "M-y": () => { + let that = this; + this.editor.yankPop().then(function(result) { + if (result) { + that.editor.setStatusBarMessage("Yank Pop"); + } else { + that.editor.setStatusBarMessage("Previous command was not a yank"); + } + }).catch(function(error) { + console.log(error); + }); + }, + "backspace": () => { + commands.executeCommand("deleteLeft").then(() => { + commands.executeCommand("emacs.exitMarkMode"); + }); + } }; }