Skip to content

Commit

Permalink
Support "{char} registers and clipboard access via "* register. (#543)
Browse files Browse the repository at this point in the history
* add register '*'

* add copy-paste to access system clipboard

* read and write to clipboard when register * is accessed

* unit tests for registers

* support alphanameric registers & update roadmap

* trying to fix ci build

* Fix gulp:tslint errors

* use new test style in register.test.ts
  • Loading branch information
aminroosta authored and johnfn committed Aug 2, 2016
1 parent 12d7927 commit 1744906
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 19 deletions.
3 changes: 2 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ Status | Command | Description

Status | Command | Description
---|--------|------------------------------
| "{char} | use register {char} for the next delete, yank, or put
:warning: | "{char} | use register {char} for the next delete, yank, or put
:white_check_mark: | "* | use register `*` to access system clipboard
| :reg | show the contents of all registers
| :reg {arg} | show the contents of registers mentioned in {arg}
:white_check_mark: | :1234: y{motion} | yank the text moved over with {motion} into a register
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
"postinstall": "node ./node_modules/vscode/bin/install && gulp init"
},
"dependencies": {
"copy-paste": "^1.3.0",
"diff": "^2.2.3",
"lodash": "^4.12.0"
},
Expand All @@ -188,4 +189,4 @@
"typings": "^1.0.4",
"vscode": "^0.11.13"
}
}
}
31 changes: 28 additions & 3 deletions src/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,31 @@ class CommandNumber extends BaseCommand {
}
}

@RegisterAction
class CommandRegister extends BaseCommand {
modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine];
keys = ["\"", "<character>"];
isCompleteAction = false;

public async exec(position: Position, vimState: VimState): Promise<VimState> {
const register = this.keysPressed[1];
vimState.recordedState.registerName = register;
return vimState;
}

public doesActionApply(vimState: VimState, keysPressed: string[]): boolean {
const register = keysPressed[1];

return super.doesActionApply(vimState, keysPressed) && Register.isValidRegister(register);
}

public couldActionApply(vimState: VimState, keysPressed: string[]): boolean {
const register = keysPressed[1];

return super.couldActionApply(vimState, keysPressed) && Register.isValidRegister(register);
}
}

@RegisterAction
class CommandEsc extends BaseCommand {
modes = [ModeName.Insert, ModeName.Visual, ModeName.VisualLine, ModeName.SearchInProgressMode];
Expand Down Expand Up @@ -892,7 +917,7 @@ export class PutCommand extends BaseCommand {
canBeRepeatedWithDot = true;

public async exec(position: Position, vimState: VimState, before: boolean = false, adjustIndent: boolean = false): Promise<VimState> {
const register = Register.get(vimState);
const register = await Register.get(vimState);
const dest = before ? position : position.getRight();
let text = register.text;

Expand Down Expand Up @@ -960,7 +985,7 @@ export class GPutCommand extends BaseCommand {
}

public async execCount(position: Position, vimState: VimState): Promise<VimState> {
const register = Register.get(vimState);
const register = await Register.get(vimState);
const addedLinesCount = register.text.split('\n').length;
const result = await super.execCount(position, vimState);

Expand Down Expand Up @@ -1079,7 +1104,7 @@ export class GPutBeforeCommand extends BaseCommand {

public async exec(position: Position, vimState: VimState): Promise<VimState> {
const result = await new PutCommand().exec(position, vimState, true);
const register = Register.get(vimState);
const register = await Register.get(vimState);
const addedLinesCount = register.text.split('\n').length;

if (vimState.effectiveRegisterMode() === RegisterMode.LineWise) {
Expand Down
7 changes: 5 additions & 2 deletions src/mode/modeHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ export class VimState {
}
}

public registerName = '"';

/**
* This is for oddball commands that don't manipulate text in any way.
*/
Expand Down Expand Up @@ -302,6 +300,11 @@ export class RecordedState {
*/
public count: number = 0;

/**
* The register name for this action.
*/
public registerName: string = '"';

public clone(): RecordedState {
const res = new RecordedState();

Expand Down
54 changes: 43 additions & 11 deletions src/register/register.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { VimState } from './../mode/modeHandler';

import * as clipboard from 'copy-paste';
/**
* There are two different modes of copy/paste in Vim - copy by character
* and copy by line. Copy by line typically happens in Visual Line mode, but
Expand All @@ -18,25 +18,39 @@ export interface IRegisterContent {
}

export class Register {
private static validRegisters = [
'"'
];

/**
* The '"' is the unnamed register.
* The '*' is the special register for stroing into system clipboard.
* TODO: Read-Only registers
* '.' register has the last inserted text.
* '%' register has the current file path.
* ':' is the most recently executed command.
* '#' is the name of last edited file. (low priority)
*/
private static registers: { [key: string]: IRegisterContent } = {
'"': { text: "", registerMode: RegisterMode.CharacterWise }
'"': { text: "", registerMode: RegisterMode.CharacterWise },
'*': { text: "", registerMode: RegisterMode.CharacterWise }
};

public static isValidRegister(register: string): boolean {
return register in Register.registers || /^[a-z0-9]+$/i.test(register);
}

/**
* Puts content in a register. If none is specified, uses the default
* register ".
*/
public static put(content: string, vimState: VimState): void {
const register = vimState.registerName;
const register = vimState.recordedState.registerName;

if (Register.validRegisters.indexOf(register) === -1) {
if (!Register.isValidRegister(register)) {
throw new Error(`Invalid register ${register}`);
}

if (register === '*') {
clipboard.copy(content);
}

Register.registers[register] = {
text : content,
registerMode: vimState.effectiveRegisterMode(),
Expand All @@ -47,13 +61,31 @@ export class Register {
* Gets content from a register. If none is specified, uses the default
* register ".
*/
public static get(vimState: VimState): IRegisterContent {
const register = vimState.registerName;
public static async get(vimState: VimState): Promise<IRegisterContent> {
const register = vimState.recordedState.registerName;

if (Register.validRegisters.indexOf(register) === -1) {
if (!Register.isValidRegister(register)) {
throw new Error(`Invalid register ${register}`);
}

if (!Register.registers[register]) {
Register.registers[register] = { text: "", registerMode: RegisterMode.CharacterWise };
}

/* Read from system clipboard */
if (register === '*') {
const text = await new Promise<string>((resolve, reject) =>
clipboard.paste((err, text) => {
if (err) {
reject(err);
} else {
resolve(text);
}
})
);
Register.registers[register].text = text;
}

return Register.registers[register];
}
}
2 changes: 1 addition & 1 deletion test/mode/modeNormal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1025,5 +1025,5 @@ suite("Mode Normal", () => {
start: ["|blah blah"],
keysPressed: "Yp",
end: ["blah blah", "|blah blah"]
})
});
});
51 changes: 51 additions & 0 deletions test/register/register.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use strict";

import { ModeHandler } from "../../src/mode/modeHandler";
import { setupWorkspace, cleanUpWorkspace, assertEqualLines } from '../testUtils';
import { getTestingFunctions } from '../testSimplifier';
import * as clipboard from 'copy-paste';

suite("register", () => {
let modeHandler: ModeHandler = new ModeHandler();

let {
newTest,
newTestOnly,
} = getTestingFunctions(modeHandler);

setup(async () => {
await setupWorkspace();
});

suiteTeardown(cleanUpWorkspace);

newTest({
title: "Can copy to a register",
start: ['|one', 'two'],
keysPressed: '"add"ap',
end: ["two", "|one"],
});

newTest({
title: "Can copy to a register",
start: ['|one', 'two'],
keysPressed: '"add"ap',
end: ["two", "|one"],
});

clipboard.copy("12345");
newTest({
title: "Can access '*' (clipboard) register",
start: ['|one'],
keysPressed: '"*P',
end: ["1234|5one"],
});

newTest({
title: "Can use two registers together",
start: ['|one', "two"],
keysPressed: '"*yyjyy"*pp',
end: ["one", "two", "one", "|two"],
});

});
3 changes: 3 additions & 0 deletions typings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
"dependencies": {
"diff": "registry:npm/diff#2.0.0+20160211003958",
"lodash": "registry:npm/lodash#4.0.0+20160416211519"
},
"globalDependencies": {
"copy-paste": "registry:dt/copy-paste#1.1.3+20160117130525"
}
}
43 changes: 43 additions & 0 deletions typings/globals/copy-paste/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Generated by typings
// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/5421783adfaf9b99e9274f4488cfc0ee73f17a56/copy-paste/copy-paste.d.ts
declare module 'copy-paste' {

export type CopyCallback = (err: Error) => void;
export type PasteCallback = (err: Error, content: string) => void;

/**
* Asynchronously replaces the current contents of the clip board with text.
*
* @param {T} content Takes either a string, array, object, or readable stream.
* @return {T} Returns the same value passed in.
*/
export function copy<T>(content: T): T;

/**
* Asynchronously replaces the current contents of the clip board with text.
*
* @param {T} content Takes either a string, array, object, or readable stream.
* @param {CopyCallback} callback will fire when the copy operation is complete.
* @return {T} Returns the same value passed in.
*/
export function copy<T>(content: T, callback: CopyCallback): T;


/**
* Synchronously returns the current contents of the system clip board.
*
* Note: The synchronous version of paste is not always availabled.
* An error message is shown if the synchronous version of paste is used on an unsupported platform.
* The asynchronous version of paste is always available.
*
* @return {string} Returns the current contents of the system clip board.
*/
export function paste(): string;

/**
* Asynchronously returns the current contents of the system clip board.
*
* @param {PasteCallback} callback The contents of the system clip board are passed to the callback as the second parameter.
*/
export function paste(callback: PasteCallback): void;
}
8 changes: 8 additions & 0 deletions typings/globals/copy-paste/typings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"resolution": "main",
"tree": {
"src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/5421783adfaf9b99e9274f4488cfc0ee73f17a56/copy-paste/copy-paste.d.ts",
"raw": "registry:dt/copy-paste#1.1.3+20160117130525",
"typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/5421783adfaf9b99e9274f4488cfc0ee73f17a56/copy-paste/copy-paste.d.ts"
}
}
1 change: 1 addition & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference path="globals/copy-paste/index.d.ts" />
/// <reference path="modules/diff/index.d.ts" />
/// <reference path="modules/lodash/index.d.ts" />
/// <reference path="vscode/index.d.ts" />
Expand Down

0 comments on commit 1744906

Please sign in to comment.