Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Numbered, upper case and multicursor register #974

Merged
merged 11 commits into from
Oct 28, 2016
18 changes: 5 additions & 13 deletions src/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ class CommandNumber extends BaseCommand {
}

@RegisterAction
class CommandRegister extends BaseCommand {
export class CommandRegister extends BaseCommand {
modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine];
keys = ["\"", "<character>"];
isCompleteAction = false;
Expand Down Expand Up @@ -1734,15 +1734,7 @@ export class YankOperator extends BaseOperator {
text = text + "\n";
}

if (!vimState.isMultiCursor) {
Register.put(text, vimState);
} else {
if (this.multicursorIndex === 0) {
Register.put([], vimState);
}

Register.add(text, vimState);
}
Register.put(text, vimState, this.multicursorIndex);

vimState.currentMode = ModeName.Normal;
vimState.cursorStartPosition = start;
Expand Down Expand Up @@ -1977,7 +1969,7 @@ export class PutCommand extends BaseCommand {
replay: "keystrokes"
});
return vimState;
} else if (typeof register.text === "object") {
} else if (typeof register.text === "object" && vimState.currentMode === ModeName.VisualBlock) {
return await this.execVisualBlockPaste(register.text, position, vimState, after);
}

Expand Down Expand Up @@ -4119,10 +4111,10 @@ export class YankVisualBlockMode extends BaseOperator {
runsOnceForEveryCursor() { return false; }

public async run(vimState: VimState, start: Position, end: Position): Promise<VimState> {
let toCopy: string[] = [];
let toCopy: string = "";

for ( const { line } of Position.IterateLine(vimState)) {
toCopy.push(line);
toCopy += line + '\n';
}

Register.put(toCopy, vimState);
Expand Down
1 change: 1 addition & 0 deletions src/cmd_line/commands/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class RegisterCommand extends node.CommandBase {
result = result.join("\n").substr(0, 100);
} else if (result instanceof RecordedState) {
// TODO
result = "";
}

return result;
Expand Down
254 changes: 232 additions & 22 deletions src/register/register.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { VimState, RecordedState } from './../mode/modeHandler';
import * as clipboard from 'copy-paste';
import { YankOperator, BaseOperator, CommandRegister, DeleteOperator } from './../actions/actions';

/**
* There are two different modes of copy/paste in Vim - copy by character
Expand Down Expand Up @@ -34,8 +35,19 @@ export class Register {
*/
private static registers: { [key: string]: IRegisterContent } = {
'"': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'.': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'*': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: true },
'+': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: true }
'+': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: true },
'0': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'1': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'2': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'3': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'4': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'5': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'6': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'7': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'8': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false },
'9': { text: "", registerMode: RegisterMode.CharacterWise, isClipboardRegister: false }
};


Expand All @@ -50,33 +62,172 @@ export class Register {
public static lastContentChange: RecordedState;

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

public static isValidRegisterForMacro(register: string): boolean {
return /^[a-zA-Z0-9]+$/i.test(register);
return /^[a-zA-Z0-9]+$/.test(register);
}

private static isValidLowercaseRegister(register: string): boolean {
return /^[a-z]+$/.test(register);
}

private static isValidUppercaseRegister(register: string): boolean {
return /^[A-Z]+$/.test(register);
}

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

if (!Register.isValidRegister(register)) {
throw new Error(`Invalid register ${register}`);
}

if (vimState.isMultiCursor) {
if (Register.isValidUppercaseRegister(register)) {
Register.appendMulticursorRegister(content, register, vimState, multicursorIndex as number);
} else {
Register.putMulticursorRegister(content, register, vimState, multicursorIndex as number);
}
} else {
if (Register.isValidUppercaseRegister(register)) {
Register.appendNormalRegister(content, register, vimState);
} else {
Register.putNormalRegister(content, register, vimState);
}
}
}

/**
* Puts the content at the specified index of the multicursor Register.
*
* `REMARKS:` This Procedure assume that you pass an valid register.
*/
private static putMulticursorRegister(content: RegisterContent, register: string, vimState: VimState, multicursorIndex: number): void {
if (multicursorIndex === 0) {
Register.registers[register.toLowerCase()] = {
text : [],
registerMode : vimState.effectiveRegisterMode(),
isClipboardRegister: Register.isClipboardRegister(register),
};
}

let registerContent = Register.registers[register.toLowerCase()];

(registerContent.text as string[]).push(content as string);

if (multicursorIndex === vimState.allCursors.length - 1) {
if (registerContent.isClipboardRegister) {
let clipboardText: string = "";

for ( const line of (registerContent.text as string[])) {
clipboardText += line + "\n";
}
clipboardText = clipboardText.replace(/\n$/, "");

clipboard.copy(clipboardText);
}

Register.ProcessNumberedRegister(registerContent.text, vimState);
}
}

/**
* Appends the content at the specified index of the multicursor Register.
*
* `REMARKS:` This Procedure assume that you pass an valid uppercase register.
*/
private static appendMulticursorRegister(content: RegisterContent, register: string, vimState: VimState, multicursorIndex: number): void {
let appendToRegister = Register.registers[register.toLowerCase()];

// Only append if appendToRegister is multicursor register
// and line count match, otherwise replace register
if (multicursorIndex === 0) {
let createEmptyRegister: boolean = false;

if (typeof appendToRegister.text === 'string') {
createEmptyRegister = true;
} else {
if ((appendToRegister.text as string[]).length !== vimState.allCursors.length) {
createEmptyRegister = true;
}
}

if (createEmptyRegister) {
Register.registers[register.toLowerCase()] = {
text : Array<string>(vimState.allCursors.length).fill(''),
registerMode : vimState.effectiveRegisterMode(),
isClipboardRegister: Register.isClipboardRegister(register),
};

appendToRegister = Register.registers[register.toLowerCase()];
}
}

let currentRegisterMode = vimState.effectiveRegisterMode();
if (appendToRegister.registerMode === RegisterMode.CharacterWise && currentRegisterMode === RegisterMode.CharacterWise) {
appendToRegister.text[multicursorIndex] += content;
} else {
appendToRegister.text[multicursorIndex] += '\n' + content;
appendToRegister.registerMode = currentRegisterMode;
}
}

/**
* Puts the content in the specified Register.
*
* `REMARKS:` This Procedure assume that you pass an valid register.
*/
private static putNormalRegister(content: RegisterContent, register: string, vimState: VimState): void {
if (Register.isClipboardRegister(register)) {
clipboard.copy(content);
}

Register.registers[register] = {
Register.registers[register.toLowerCase()] = {
text : content,
registerMode : vimState.effectiveRegisterMode(),
isClipboardRegister: Register.isClipboardRegister(register),
};

Register.ProcessNumberedRegister(content, vimState);
}

/**
* Appends the content at the specified index of the multicursor Register.
*
* `REMARKS:` This Procedure assume that you pass an valid uppercase register.
*/
private static appendNormalRegister(content: RegisterContent, register: string, vimState: VimState): void {
let appendToRegister = Register.registers[register.toLowerCase()];
let currentRegisterMode = vimState.effectiveRegisterMode();

// Check if appending to a multicursor register or normal
if (appendToRegister.text instanceof Array) {
if (appendToRegister.registerMode === RegisterMode.CharacterWise && currentRegisterMode === RegisterMode.CharacterWise) {
for (let i = 0; i < appendToRegister.text.length; i++) {
appendToRegister.text[i] += content;
}
} else {
for (let i = 0; i < appendToRegister.text.length; i++) {
appendToRegister.text[i] += '\n' + content;
}
appendToRegister.registerMode = currentRegisterMode;
}
} else if (typeof appendToRegister.text === 'string') {
if (appendToRegister.registerMode === RegisterMode.CharacterWise && currentRegisterMode === RegisterMode.CharacterWise) {
appendToRegister.text = appendToRegister.text + content;
} else {
appendToRegister.text += '\n' + content;
appendToRegister.registerMode = currentRegisterMode;
}
}
}

public static putByKey(content: RegisterContent, register = '"', registerMode = RegisterMode.FigureItOutFromCurrentMode): void {
Expand All @@ -95,39 +246,58 @@ export class Register {
};
}

public static add(content: string, vimState: VimState): void {
const register = vimState.recordedState.registerName;
/**
* Handles special cases for Yank- and DeleteOperator.
*/
private static ProcessNumberedRegister(content: RegisterContent, vimState: VimState): void {
// Find the BaseOperator of the current actions
const baseOperator = vimState.recordedState.actionsRun.find( (value) => {
return value instanceof BaseOperator;
});

if (!Register.isValidRegister(register)) {
throw new Error(`Invalid register ${register}`);
}
if (baseOperator instanceof YankOperator) {
// 'yank' to 0 only if no register was specified
const registerCommand = vimState.recordedState.actionsRun.find( (value) => {
return value instanceof CommandRegister;
});

if (typeof Register.registers[register].text !== "string") {
// TODO - I don't know why this cast is necessary!
if (!registerCommand) {
Register.registers['0'].text = content;
Register.registers['0'].registerMode = vimState.effectiveRegisterMode();
}
} else if (baseOperator instanceof DeleteOperator) {
// shift 'delete-history' register
for (let index = 9; index > 1; index--) {
Register.registers[String(index)].text = Register.registers[String(index - 1)].text;
Register.registers[String(index)].registerMode = Register.registers[String(index - 1)].registerMode;
}

(Register.registers[register].text as string[]).push(content);
// Paste last delete into register '1'
Register.registers['1'].text = content;
Register.registers['1'].registerMode = vimState.effectiveRegisterMode();
}
}


/**
* Gets content from a register. If none is specified, uses the default
* register ".
*/
public static async get(vimState: VimState): Promise<IRegisterContent> {
const register = vimState.recordedState.registerName;
return Register.getByKey(register);
return Register.getByKey(register, vimState);
}

public static async getByKey(register: string): Promise<IRegisterContent> {
public static async getByKey(register: string, vimState?: VimState): Promise<IRegisterContent> {
if (!Register.isValidRegister(register)) {
throw new Error(`Invalid register ${register}`);
}

let lowercaseRegister = register.toLowerCase();

// Clipboard registers are always defined, so if a register doesn't already
// exist we can be sure it's not a clipboard one
if (!Register.registers[register]) {
Register.registers[register] = {
if (!Register.registers[lowercaseRegister]) {
Register.registers[lowercaseRegister] = {
text : "",
registerMode : RegisterMode.CharacterWise,
isClipboardRegister: false
Expand All @@ -136,7 +306,7 @@ export class Register {

/* Read from system clipboard */
if (Register.isClipboardRegister(register)) {
const text = await new Promise<string>((resolve, reject) =>
let text = await new Promise<string>((resolve, reject) =>
clipboard.paste((err, text) => {
if (err) {
reject(err);
Expand All @@ -146,10 +316,49 @@ export class Register {
})
);

Register.registers[register].text = text;
}
// Harmonize newline character
text = text.replace(/\r\n/g, '\n');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we read files.eol to use the proper line ending of the file? https://code.visualstudio.com/Docs/customization/userandworkspace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines only apply when we read from the system clipboard. The problem is vscode automaticaly transforms \n to \r\n when your EOL is set to CRLF, so when we're on a windows machine the EOL is \r\n if i would paste that vscode transforms it to \r\r\n and you've got two linebreaks in the editor.
My first idea was to get the current EOL setting from the active editor but i didn't found the vscode API, you can set it but i don't found where i can read the setting???
As i can't go that way, my next idea was check the OS if we're on windows replace the newline char with the vscode-vim internal newline ('\n' we use it everywhere), but then i thought safe me the check and replace it everytime because on windows it fixes the double newline and on unix systems it causes no harm because there is no \r\n in the clipboard.

What do you think, is it better to add the OS check so it is more obvious that it should only apply to windows machines?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following should work:

let eol = vscode.workspace.getConfiguration('files').get("eol", "\n");

and then you can add this as a new member to configuration.ts to expose it to your current functionality.

Copy link
Contributor Author

@Platzer Platzer Oct 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So when i think about it the "The default end of line character" (comment for the setting) is not the problem, i would need the "current system clipboard end of line character" because i had to romve the \r only on windows because vscode adds it when \n is inserted. And in case of unix system replacing \n with \n is unnecessary.
See the behavior i get on my machine (WIN7 with EOL = CRLF):
vscodeinsertlinefeed
Every replace i do the \n is turned into \r\n so \n is the universial newline character in vscode?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, very interesting behaviour. I repro that on my mac and I see what you mean, in that case using \n is the way to go.


let registerText: string | string[];
if (vimState && vimState.isMultiCursor) {
registerText = text.split('\n');
if (registerText.length !== vimState.allCursors.length) {
registerText = text;
}
} else {
registerText = text;
}

return Register.registers[register];
Register.registers[lowercaseRegister].text = registerText;
return Register.registers[register];
} else {
let text = Register.registers[lowercaseRegister].text;

let registerText: RegisterContent;
if (text instanceof RecordedState) {
registerText = text;
} else {
if (vimState && vimState.isMultiCursor && (typeof text === 'object')) {
if ((text as string[]).length === vimState.allCursors.length) {
registerText = text;
} else {
registerText = (text as string[]).join('\n');
}
} else {
if (typeof text === 'object') {
registerText = (text as string[]).join('\n');
} else {
registerText = text;
}
}
}

return {
text : registerText,
registerMode : Register.registers[lowercaseRegister].registerMode,
isClipboardRegister: Register.registers[lowercaseRegister].isClipboardRegister
};
}
}

public static has(register: string): boolean {
Expand All @@ -160,3 +369,4 @@ export class Register {
return Object.keys(Register.registers);
}
}

Loading