Skip to content

Commit

Permalink
Merge pull request #3071 from captaincaius/relative-command-ranges
Browse files Browse the repository at this point in the history
feature: relative, plus/minus ranges. closes #2384
  • Loading branch information
xconverge authored Oct 11, 2018
2 parents de9dfb1 + 340452a commit 00bb9af
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 53 deletions.
6 changes: 5 additions & 1 deletion src/actions/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1774,7 +1774,11 @@ class CommandShowCommandLine extends BaseCommand {

public async exec(position: Position, vimState: VimState): Promise<VimState> {
if (vimState.currentMode === ModeName.Normal) {
vimState.currentCommandlineText = '';
if (vimState.recordedState.count) {
vimState.currentCommandlineText = `.,.+${vimState.recordedState.count - 1}`;
} else {
vimState.currentCommandlineText = '';
}
} else {
vimState.currentCommandlineText = "'<,'>";
}
Expand Down
6 changes: 5 additions & 1 deletion src/cmd_line/commands/substitute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,11 @@ export class SubstituteCommand extends node.CommandBase {
endLine = new vscode.Position(TextEditor.getLineCount() - 1, 0);
} else {
startLine = range.lineRefToPosition(vimState.editor, range.left, vimState);
endLine = range.lineRefToPosition(vimState.editor, range.right, vimState);
if (range.right.length === 0) {
endLine = startLine;
} else {
endLine = range.lineRefToPosition(vimState.editor, range.right, vimState);
}
}

if (this._arguments.count && this._arguments.count >= 0) {
Expand Down
71 changes: 45 additions & 26 deletions src/cmd_line/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,18 @@ namespace LexerFunctions {
case '7':
case '8':
case '9':
return lexLineRef;
if (tokens.length < 1) {
// special case - first digitey token is always a line number
return lexDigits(TokenType.LineNumber);
} else {
// otherwise, use previous token to determine which flavor of digit lexer should be used
const previousTokenType = tokens[tokens.length - 1].type;
if (previousTokenType === TokenType.Plus || previousTokenType === TokenType.Minus) {
return lexDigits(TokenType.Offset);
} else {
return lexDigits(TokenType.LineNumber);
}
}
case '+':
tokens.push(emitToken(TokenType.Plus, state)!);
continue;
Expand Down Expand Up @@ -110,33 +121,41 @@ namespace LexerFunctions {
return lexRange;
}

function lexLineRef(state: Scanner, tokens: Token[]): ILexFunction | null {
// The first digit has already been lexed.
while (true) {
if (state.isAtEof) {
tokens.push(emitToken(TokenType.LineNumber, state)!);
return null;
}
/**
* when we're lexing digits, it could either be a line number or an offset, depending on whether
* our previous token was a + or a -
*
* so it's lexRange's job to specify which token to emit.
*/
function lexDigits(tokenType: TokenType) {
return function(state: Scanner, tokens: Token[]): ILexFunction | null {
// The first digit has already been lexed.
while (true) {
if (state.isAtEof) {
tokens.push(emitToken(tokenType, state)!);
return null;
}

var c = state.next();
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
continue;
default:
state.backup();
tokens.push(emitToken(TokenType.LineNumber, state)!);
return lexRange;
var c = state.next();
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
continue;
default:
state.backup();
tokens.push(emitToken(tokenType, state)!);
return lexRange;
}
}
}
};
}

function lexCommand(state: Scanner, tokens: Token[]): ILexFunction | null {
Expand Down
135 changes: 116 additions & 19 deletions src/cmd_line/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as vscode from 'vscode';
import { VimState } from '../state/vimState';
import * as token from './token';

type LineRefOperation = token.TokenType.Plus | token.TokenType.Minus | undefined;

export class LineRange {
left: token.Token[];
separator: token.Token;
Expand All @@ -20,15 +22,27 @@ export class LineRange {
}

if (!this.separator) {
if (this.left.length > 0 && tok.type !== token.TokenType.Offset) {
// XXX: is this always this error?
throw Error('not a Vim command');
if (this.left.length > 0) {
switch (tok.type) {
case token.TokenType.Offset:
case token.TokenType.Plus:
case token.TokenType.Minus:
break;
default:
throw Error('Trailing characters');
}
}
this.left.push(tok);
} else {
if (this.right.length > 0 && tok.type !== token.TokenType.Offset) {
// XXX: is this always this error?
throw Error('not a Vim command');
if (this.right.length > 0) {
switch (tok.type) {
case token.TokenType.Offset:
case token.TokenType.Plus:
case token.TokenType.Minus:
break;
default:
throw Error('Trailing characters');
}
}
this.right.push(tok);
}
Expand Down Expand Up @@ -57,20 +71,30 @@ export class LineRange {
toks: token.Token[],
vimState: VimState
): vscode.Position {
var first = toks[0];
switch (first.type) {
case token.TokenType.Dollar:
let currentLineNum: number;
let currentColumn = 0; // only mark does this differently
let currentOperation: LineRefOperation = undefined;

const firstToken = toks[0];
// handle first-token special cases (e.g. %, inital line number is "." by default)
switch (firstToken.type) {
case token.TokenType.Percent:
return new vscode.Position(doc.document.lineCount - 1, 0);
case token.TokenType.Dollar:
currentLineNum = doc.document.lineCount - 1;
break;
case token.TokenType.Plus:
case token.TokenType.Minus:
case token.TokenType.Dot:
return new vscode.Position(doc.selection.active.line, 0);
currentLineNum = doc.selection.active.line;
// undocumented: if the first token is plus or minus, vim seems to behave as though there was a "."
currentOperation = firstToken.type === token.TokenType.Dot ? undefined : firstToken.type;
break;
case token.TokenType.LineNumber:
var line = Number.parseInt(first.content, 10);
line = Math.max(0, line - 1);
line = Math.min(doc.document.lineCount, line);
return new vscode.Position(line, 0);
currentLineNum = Number.parseInt(firstToken.content, 10) - 1; // user sees 1-based - everything else is 0-based
break;
case token.TokenType.SelectionFirstLine:
let startLine = Math.min.apply(
currentLineNum = Math.min.apply(
null,
doc.selections.map(
selection =>
Expand All @@ -79,21 +103,94 @@ export class LineRange {
: selection.end.line
)
);
return new vscode.Position(startLine, 0);
break;
case token.TokenType.SelectionLastLine:
let endLine = Math.max.apply(
currentLineNum = Math.max.apply(
null,
doc.selections.map(
selection =>
selection.start.isAfter(selection.end) ? selection.start.line : selection.end.line
)
);
return new vscode.Position(endLine, 0);
break;
case token.TokenType.Mark:
return vimState.historyTracker.getMark(first.content).position;
currentLineNum = vimState.historyTracker.getMark(firstToken.content).position.line;
currentColumn = vimState.historyTracker.getMark(firstToken.content).position.character;
break;
default:
throw new Error('Not Implemented');
}

// now handle subsequent tokens, offsetting the current candidate line number
for (let tokenIndex = 1; tokenIndex < toks.length; ++tokenIndex) {
let currentToken = toks[tokenIndex];

switch (currentOperation) {
case token.TokenType.Plus:
switch (currentToken.type) {
case token.TokenType.Minus:
case token.TokenType.Plus:
// undocumented: when there's two operators in a row, vim behaves as though there's a "1" between them
currentLineNum += 1;
currentColumn = 0;
currentOperation = currentToken.type;
break;
case token.TokenType.Offset:
currentLineNum += Number.parseInt(currentToken.content, 10);
currentColumn = 0;
currentOperation = undefined;
break;
default:
throw Error('Trailing characters');
}
break;
case token.TokenType.Minus:
switch (currentToken.type) {
case token.TokenType.Minus:
case token.TokenType.Plus:
// undocumented: when there's two operators in a row, vim behaves as though there's a "1" between them
currentLineNum -= 1;
currentColumn = 0;
currentOperation = currentToken.type;
break;
case token.TokenType.Offset:
currentLineNum -= Number.parseInt(currentToken.content, 10);
currentColumn = 0;
currentOperation = undefined;
break;
default:
throw Error('Trailing characters');
}
break;
case undefined:
switch (currentToken.type) {
case token.TokenType.Minus:
case token.TokenType.Plus:
currentOperation = currentToken.type;
break;
default:
throw Error('Trailing characters');
}
break;
}
}

// undocumented: when there's a trailing operation in the tank without an RHS, vim uses "1"
switch (currentOperation) {
case token.TokenType.Plus:
currentLineNum += 1;
currentColumn = 0;
break;
case token.TokenType.Minus:
currentLineNum -= 1;
currentColumn = 0;
break;
}

// finally, make sure current position is in bounds :)
currentLineNum = Math.max(0, currentLineNum);
currentLineNum = Math.min(doc.document.lineCount - 1, currentLineNum);
return new vscode.Position(currentLineNum, currentColumn);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/cmd_line/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ function parseLineRange(state: ParserState, commandLine: node.CommandLine): IPar
case token.TokenType.SelectionFirstLine:
case token.TokenType.SelectionLastLine:
case token.TokenType.Mark:
case token.TokenType.Offset:
case token.TokenType.Plus:
case token.TokenType.Minus:
commandLine.range.addToken(tok);
continue;
case token.TokenType.CommandName:
Expand Down
6 changes: 2 additions & 4 deletions src/cmd_line/subparsers/substitute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ function parseSubstituteFlags(scanner: Scanner): number {
flags = flags | node.SubstituteFlags.UsePreviousPattern;
break;
default:
return node.SubstituteFlags.None;
scanner.backup();
return flags;
}

index++;
Expand Down Expand Up @@ -139,7 +140,6 @@ export function parseSubstituteCommandArgs(args: string): node.SubstituteCommand
pattern: undefined,
replace: '', // ignored in this context
flags: node.SubstituteFlags.None,
count: 1,
});
}
let scanner: Scanner;
Expand All @@ -153,7 +153,6 @@ export function parseSubstituteCommandArgs(args: string): node.SubstituteCommand
pattern: '',
replace: '',
flags: node.SubstituteFlags.None,
count: 1,
});
}

Expand All @@ -168,7 +167,6 @@ export function parseSubstituteCommandArgs(args: string): node.SubstituteCommand
pattern: searchPattern,
replace: '',
flags: node.SubstituteFlags.None,
count: 1,
});
}
replaceString = parsePattern('', scanner, delimiter)[0];
Expand Down
Loading

0 comments on commit 00bb9af

Please sign in to comment.