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

Fixes #1534, #1518, #1716, #1618, #1450: Refactored repeating motions #1712

Merged
merged 21 commits into from
May 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,161 changes: 2,160 additions & 1 deletion CHANGELOG.md

Large diffs are not rendered by default.

20 changes: 2 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,26 +415,10 @@ Some examples:

Commentary in VSCodeVim works similarly to tpope's [vim-commentary](https://github.com/tpope/vim-commentary) but uses the VSCode native "Toggle Line Comment" and "Toggle Block Comment" features.

> ⚠️ Because [`gc`](#vscodevim-tricks) is already used in VSCodeVim, the commentary operators are bound to `gb` for line comments and `gB` for block comments.

Usage examples:
* `gb` - toggles line comment. For example `gbb` to toggle line comment for current line and `gb2j` to toggle line comments for the current line and the next line.
* `gB` - toggles block comment. For example `gBi)` to comment out everything within parenthesis.

If you are use to using vim-commentary you are probably use to using `gc` instead of `gb`. This can be achieved by adding the following remapping to your VSCode settings:
* `gc` - toggles line comment. For example `gcc` to toggle line comment for current line and `gc2j` to toggle line comments for the current line and the next line.
* `gC` - toggles block comment. For example `gCi)` to comment out everything within parenthesis.

```
"vim.otherModesKeyBindings": [
{
"before": ["g", "c"],
"after": ["g", "b"]
},
{
"before": ["g", "C"],
"after": ["g", "B"]
}
],
```

### vim-indent-object

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,11 @@
"type": "boolean",
"description": "Uses a hack to move around folds properly",
"default": false
},
"vim.disableAnnoyingGcComment": {
"type": "boolean",
"description": "Get rid of that annoying pop up that shows up everytime you type gc or gb",
"default": false
}
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/actions/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export class BaseAction {

public mustBeFirstKey = false;

public isOperator = false;

/**
* The keys pressed at the time that this action was triggered.
*/
Expand All @@ -92,7 +94,7 @@ export class BaseAction {
public doesActionApply(vimState: VimState, keysPressed: string[]): boolean {
if (this.modes.indexOf(vimState.currentMode) === -1) { return false; }
if (!compareKeypressSequence(this.keys, keysPressed)) { return false; }
if (vimState.recordedState.actionsRun.length > 0 &&
if (vimState.recordedState.getCurrentCommandWithoutCountPrefix().length - keysPressed.length > 0 &&
this.mustBeFirstKey) { return false; }

return true;
Expand All @@ -104,12 +106,14 @@ export class BaseAction {
public couldActionApply(vimState: VimState, keysPressed: string[]): boolean {
if (this.modes.indexOf(vimState.currentMode) === -1) { return false; }
if (!compareKeypressSequence(this.keys.slice(0, keysPressed.length), keysPressed)) { return false; }
if (vimState.recordedState.actionsRun.length > 0 &&
if (vimState.recordedState.getCurrentCommandWithoutCountPrefix().length - keysPressed.length > 0 &&
this.mustBeFirstKey) { return false; }


return true;
}


public toString(): string {
return this.keys.join("");
}
Expand Down
235 changes: 6 additions & 229 deletions src/actions/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,7 @@ class CommandChangeToLineEnd extends BaseCommand {
modes = [ModeName.Normal];
keys = ["C"];
runsOnceForEachCountPrefix = false;
mustBeFirstKey = true;

public async exec(position: Position, vimState: VimState): Promise<VimState> {
let count = vimState.recordedState.count || 1;
Expand Down Expand Up @@ -2484,234 +2485,6 @@ class ActionJoinVisualMode extends BaseCommand {
}
}

interface CommentTypeSingle {
singleLine: true;

start: string;
}

interface CommentTypeMultiLine {
singleLine: false;

start: string;
inner: string;
final: string;
}

type CommentType = CommentTypeSingle | CommentTypeMultiLine;

@RegisterAction
class ActionVisualReflowParagraph extends BaseCommand {
modes = [ModeName.Visual, ModeName.VisualLine];
keys = ["g", "q"];

public static CommentTypes: CommentType[] = [
{ singleLine: true, start: "///"},
{ singleLine: true, start: "//" },
{ singleLine: true, start: "--" },
{ singleLine: true, start: "#" },
{ singleLine: true, start: ";" },
{ singleLine: false, start: "/**", inner: "*", final: "*/" },
{ singleLine: false, start: "/*", inner: "*", final: "*/" },
{ singleLine: false, start: "{-", inner: "-", final: "-}" },

// Needs to come last, since everything starts with the emtpy string!
{ singleLine: true, start: "" },
];

public getIndentationLevel(s: string): number {
for (const line of s.split("\n")) {
const result = line.match(/^\s+/g);
const indentLevel = result ? result[0].length : 0;

if (indentLevel !== line.length) {
return indentLevel;
}
}

return 0;
}

public reflowParagraph(s: string, indentLevel: number): string {
const maximumLineLength = Configuration.textwidth - indentLevel - 2;
const indent = Array(indentLevel + 1).join(" ");

// Chunk the lines by commenting style.

let chunksToReflow: {
commentType: CommentType;
content: string;
indentLevelAfterComment: number;
}[] = [];

for (const line of s.split("\n")) {
let lastChunk: { commentType: CommentType; content: string} | undefined = chunksToReflow[chunksToReflow.length - 1];
const trimmedLine = line.trim();

// See what comment type they are using.

let commentType: CommentType | undefined;

for (const type of ActionVisualReflowParagraph.CommentTypes) {
if (line.trim().startsWith(type.start)) {
commentType = type;

break;
}

// If they're currently in a multiline comment, see if they continued it.
if (lastChunk && type.start === lastChunk.commentType.start && !type.singleLine) {
if (line.trim().startsWith(type.inner)) {
commentType = type;

break;
}

if (line.trim().endsWith(type.final)) {
commentType = type;

break;
}
}
}

if (!commentType) { break; } // will never happen, just to satisfy typechecker.

// Did they start a new comment type?
if (!lastChunk || commentType.start !== lastChunk.commentType.start) {
let chunk = {
commentType,
content: `${ trimmedLine.substr(commentType.start.length).trim() }`,
indentLevelAfterComment: 0
};
if (commentType.singleLine) {
chunk.indentLevelAfterComment = trimmedLine.substr(commentType.start.length).length - chunk.content.length;
}
chunksToReflow.push(chunk);

continue;
}

// Parse out commenting style, gather words.

lastChunk = chunksToReflow[chunksToReflow.length - 1];

if (lastChunk.commentType.singleLine) { // is it a continuation of a comment like "//"
lastChunk.content += `\n${ trimmedLine.substr(lastChunk.commentType.start.length).trim() }`;

} else { // are we in the middle of a multiline comment like "/*"
if (trimmedLine.endsWith(lastChunk.commentType.final)) {
if (trimmedLine.length > lastChunk.commentType.final.length) {
lastChunk.content += `\n${ trimmedLine.substr(
lastChunk.commentType.inner.length,
trimmedLine.length - lastChunk.commentType.final.length
).trim() }`;
}

} else if (trimmedLine.startsWith(lastChunk.commentType.inner)) {
lastChunk.content += `\n${ trimmedLine.substr(lastChunk.commentType.inner.length).trim() }`;
} else if (trimmedLine.startsWith(lastChunk.commentType.start)) {
lastChunk.content += `\n${ trimmedLine.substr(lastChunk.commentType.start.length).trim() }`;
}
}
}

// Reflow each chunk.
let result: string[] = [];

for (const { commentType, content, indentLevelAfterComment } of chunksToReflow) {
let lines: string[];
const indentAfterComment = Array(indentLevelAfterComment + 1).join(" ");

if (commentType.singleLine) {
lines = [``];
} else {
lines = [``, ``];
}

for (const line of content.trim().split("\n")) {

// Preserve newlines.

if (line.trim() === "") {
for (let i = 0; i < 2; i++) {
lines.push(``);
}

continue;
}

// Add word by word, wrapping when necessary.
const words = line.split(/\s+/);
for (let i = 0; i < words.length; i++) {
const word = words[i];
if (word === "") { continue; }

if (lines[lines.length - 1].length + word.length + 1 < maximumLineLength) {
if (i) {
lines[lines.length - 1] += ` ${ word }`;
} else {
lines[lines.length - 1] += `${ word }`;
}
} else {
lines.push(`${ word }`);
}
}
}

if (!commentType.singleLine) {
lines.push(``);
}

if (commentType.singleLine) {
if (lines.length > 1 && lines[0].trim() === "") { lines = lines.slice(1); }
if (lines.length > 1 && lines[lines.length - 1].trim() === "") { lines = lines.slice(0, -1); }
}

for (let i = 0; i < lines.length; i++) {
if (commentType.singleLine) {
lines[i] = `${ indent }${ commentType.start }${ indentAfterComment }${ lines[i] }`;
} else {
if (i === 0) {
lines[i] = `${ indent }${ commentType.start } ${ lines[i] }`;
} else if (i === lines.length - 1) {
lines[i] = `${ indent } ${ commentType.final }`;
} else {
lines[i] = `${ indent } ${ commentType.inner } ${ lines[i] }`;
}
}
}

result = result.concat(lines);
}

// Gather up multiple empty lines into single empty lines.
return result.join("\n");
}

public async exec(position: Position, vimState: VimState): Promise<VimState> {
const rangeStart = Position.EarlierOf(vimState.cursorPosition, vimState.cursorStartPosition);
const rangeStop = Position.LaterOf(vimState.cursorPosition, vimState.cursorStartPosition);

let textToReflow = TextEditor.getText(new vscode.Range(rangeStart, rangeStop));
let indentLevel = this.getIndentationLevel(textToReflow);

textToReflow = this.reflowParagraph(textToReflow, indentLevel);

vimState.recordedState.transformations.push({
type: "replaceText",
text: textToReflow,
start: vimState.cursorStartPosition,
end: vimState.cursorPosition,
// Move cursor to front of line to realign the view
diff: PositionDiff.NewBOLDiff(0, 0)
});

vimState.currentMode = ModeName.Normal;

return vimState;
}
}

@RegisterAction
class ActionJoinNoWhitespace extends BaseCommand {
Expand Down Expand Up @@ -3274,12 +3047,16 @@ class ActionOverrideCmdD extends BaseCommand {
modes = [ModeName.Normal, ModeName.Visual];
keys = [
["<D-d>"],
["g", "c"]
["g", "b"]
];
runsOnceForEveryCursor() { return false; }
runsOnceForEachCountPrefix = true;

public async exec(position: Position, vimState: VimState): Promise<VimState> {
if (!Configuration.disableAnnoyingGcComment) {
vscode.window.showErrorMessage("gc is now commentOperator. gb is now 'add new cursor'.\
Disable this annoying message with vim.disableAnnoyingGcComment");
}
await vscode.commands.executeCommand('editor.action.addSelectionToNextFindMatch');
vimState.allCursors = await allowVSCodeToPropagateCursorUpdatesAndReturnThem();

Expand Down
Loading