mirror of
https://github.com/VSCodeVim/Vim.git
synced 2024-09-20 16:48:42 +03:00
implement :w!
This commit is contained in:
parent
3e151bfb16
commit
a765d5a061
@ -1,28 +1,79 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as node from './node';
|
||||
import * as util from '../util';
|
||||
import fs = require('fs');
|
||||
|
||||
import vscode = require('vscode');
|
||||
|
||||
import node = require('./node');
|
||||
import util = require('../util');
|
||||
|
||||
export interface WriteCommandArguments {
|
||||
opt? : string;
|
||||
optValue? : string;
|
||||
bang? : boolean;
|
||||
range? : node.LineRange;
|
||||
file? : string;
|
||||
append? : boolean;
|
||||
cmd? : string;
|
||||
}
|
||||
|
||||
//
|
||||
// Implements :write
|
||||
// http://vimdoc.sourceforge.net/htmldoc/editing.html#:write
|
||||
//
|
||||
export class WriteCommand implements node.CommandBase {
|
||||
name : string;
|
||||
shortName : string;
|
||||
args : Object;
|
||||
name : string;
|
||||
shortName : string;
|
||||
args : WriteCommandArguments;
|
||||
|
||||
constructor(args : Object = null) {
|
||||
// TODO: implement other arguments.
|
||||
this.name = 'write';
|
||||
this.shortName = 'w';
|
||||
this.args = args;
|
||||
}
|
||||
constructor(args : WriteCommandArguments = {}) {
|
||||
this.name = 'write';
|
||||
this.shortName = 'w';
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
runOn(textEditor : vscode.TextEditor) : void {
|
||||
if (this.args) {
|
||||
util.showError("Not implemented.");
|
||||
return;
|
||||
}
|
||||
textEditor.document.save();
|
||||
}
|
||||
private doSave(textEditor : vscode.TextEditor) {
|
||||
textEditor.document.save().then(
|
||||
(ok) => {
|
||||
if (ok) {
|
||||
util.showInfo("File saved.");
|
||||
} else {
|
||||
util.showError("File not saved.");
|
||||
}
|
||||
},
|
||||
(e) => util.showError(e)
|
||||
);
|
||||
};
|
||||
|
||||
runOn(textEditor : vscode.TextEditor) : void {
|
||||
if (this.args.opt) {
|
||||
util.showError("Not implemented.");
|
||||
return;
|
||||
} else if (this.args.file) {
|
||||
util.showError("Not implemented.");
|
||||
return;
|
||||
} else if (this.args.append) {
|
||||
util.showError("Not implemented.");
|
||||
return;
|
||||
} else if (this.args.cmd) {
|
||||
util.showError("Not implemented.");
|
||||
return;
|
||||
}
|
||||
|
||||
fs.access(textEditor.document.fileName, fs.W_OK, (accessErr) => {
|
||||
if (accessErr) {
|
||||
if (this.args.bang) {
|
||||
fs.chmod(textEditor.document.fileName, 666, (e) => {
|
||||
if (e) {
|
||||
util.showError(e.message);
|
||||
} else {
|
||||
this.doSave(textEditor);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
util.showError(accessErr.message);
|
||||
}
|
||||
} else {
|
||||
this.doSave(textEditor);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,182 +3,182 @@ import {Token, TokenType} from "./token";
|
||||
|
||||
// Describes a function that can lex part of a Vim command line.
|
||||
interface LexFunction {
|
||||
(state: Scanner, tokens: Token[]) : LexFunction;
|
||||
(state: Scanner, tokens: Token[]) : LexFunction;
|
||||
}
|
||||
|
||||
export function lex(input : string) : Token[] {
|
||||
// We use a character scanner as state for the lexer.
|
||||
var state = new Scanner(input);
|
||||
var tokens : Token[] = [];
|
||||
var f : LexFunction = LexerFunctions.lexRange;
|
||||
while (f) {
|
||||
// Each lexing function returns the next lexing function or null.
|
||||
f = f(state, tokens);
|
||||
}
|
||||
return tokens;
|
||||
// We use a character scanner as state for the lexer.
|
||||
var state = new Scanner(input);
|
||||
var tokens : Token[] = [];
|
||||
var f : LexFunction = LexerFunctions.lexRange;
|
||||
while (f) {
|
||||
// Each lexing function returns the next lexing function or null.
|
||||
f = f(state, tokens);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function emitToken(type : TokenType, state : Scanner) : Token {
|
||||
var content = state.emit();
|
||||
return (content.length > 0) ? new Token(type, content) : null;
|
||||
var content = state.emit();
|
||||
return (content.length > 0) ? new Token(type, content) : null;
|
||||
}
|
||||
|
||||
module LexerFunctions {
|
||||
// Starts lexing a Vim command line and delegates on other lexer functions as needed.
|
||||
export function lexRange(state : Scanner, tokens : Token[]): LexFunction {
|
||||
while (true) {
|
||||
if (state.isAtEof) {
|
||||
break;
|
||||
}
|
||||
var c = state.next();
|
||||
switch (c) {
|
||||
case ",":
|
||||
tokens.push(emitToken(TokenType.Comma, state));
|
||||
continue;
|
||||
case "%":
|
||||
tokens.push(emitToken(TokenType.Percent, state));
|
||||
continue;
|
||||
case "$":
|
||||
tokens.push(emitToken(TokenType.Dollar, state));
|
||||
continue;
|
||||
case ".":
|
||||
tokens.push(emitToken(TokenType.Dot, state));
|
||||
continue;
|
||||
case "/":
|
||||
return lexForwardSearch;
|
||||
case "?":
|
||||
return lexReverseSearch;
|
||||
case "0":
|
||||
case "1":
|
||||
case "2":
|
||||
case "3":
|
||||
case "4":
|
||||
case "5":
|
||||
case "6":
|
||||
case "7":
|
||||
case "8":
|
||||
case "9":
|
||||
return lexLineRef;
|
||||
case "+":
|
||||
tokens.push(emitToken(TokenType.Plus, state));
|
||||
continue;
|
||||
case "-":
|
||||
tokens.push(emitToken(TokenType.Minus, state));
|
||||
continue;
|
||||
default:
|
||||
return lexCommand;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Starts lexing a Vim command line and delegates on other lexer functions as needed.
|
||||
export function lexRange(state : Scanner, tokens : Token[]): LexFunction {
|
||||
while (true) {
|
||||
if (state.isAtEof) {
|
||||
break;
|
||||
}
|
||||
var c = state.next();
|
||||
switch (c) {
|
||||
case ",":
|
||||
tokens.push(emitToken(TokenType.Comma, state));
|
||||
continue;
|
||||
case "%":
|
||||
tokens.push(emitToken(TokenType.Percent, state));
|
||||
continue;
|
||||
case "$":
|
||||
tokens.push(emitToken(TokenType.Dollar, state));
|
||||
continue;
|
||||
case ".":
|
||||
tokens.push(emitToken(TokenType.Dot, state));
|
||||
continue;
|
||||
case "/":
|
||||
return lexForwardSearch;
|
||||
case "?":
|
||||
return lexReverseSearch;
|
||||
case "0":
|
||||
case "1":
|
||||
case "2":
|
||||
case "3":
|
||||
case "4":
|
||||
case "5":
|
||||
case "6":
|
||||
case "7":
|
||||
case "8":
|
||||
case "9":
|
||||
return lexLineRef;
|
||||
case "+":
|
||||
tokens.push(emitToken(TokenType.Plus, state));
|
||||
continue;
|
||||
case "-":
|
||||
tokens.push(emitToken(TokenType.Minus, state));
|
||||
continue;
|
||||
default:
|
||||
return lexCommand;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function lexLineRef(state : Scanner, tokens : Token[]): LexFunction {
|
||||
// The first digit has already been lexed.
|
||||
while (true) {
|
||||
if (state.isAtEof) {
|
||||
tokens.push(emitToken(TokenType.LineNumber, 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;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function lexLineRef(state : Scanner, tokens : Token[]): LexFunction {
|
||||
// The first digit has already been lexed.
|
||||
while (true) {
|
||||
if (state.isAtEof) {
|
||||
tokens.push(emitToken(TokenType.LineNumber, 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;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function lexCommand(state : Scanner, tokens : Token[]): LexFunction {
|
||||
// The first character of the command's name has already been lexed.
|
||||
while (true) {
|
||||
if (state.isAtEof) {
|
||||
tokens.push(emitToken(TokenType.CommandName, state));
|
||||
break;
|
||||
}
|
||||
var c = state.next();
|
||||
var lc = c.toLowerCase();
|
||||
if (lc >= "a" && lc <= "z") {
|
||||
continue;
|
||||
} else {
|
||||
state.backup();
|
||||
tokens.push(emitToken(TokenType.CommandName, state));
|
||||
state.skipWhiteSpace();
|
||||
while (!state.isAtEof) {
|
||||
state.next();
|
||||
}
|
||||
// TODO(guillermooo): We need to parse multiple commands.
|
||||
var args = emitToken(TokenType.CommandArgs, state);
|
||||
if (args) {
|
||||
tokens.push(args);
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function lexCommand(state : Scanner, tokens : Token[]): LexFunction {
|
||||
// The first character of the command's name has already been lexed.
|
||||
while (true) {
|
||||
if (state.isAtEof) {
|
||||
tokens.push(emitToken(TokenType.CommandName, state));
|
||||
break;
|
||||
}
|
||||
var c = state.next();
|
||||
var lc = c.toLowerCase();
|
||||
if (lc >= "a" && lc <= "z") {
|
||||
continue;
|
||||
} else {
|
||||
state.backup();
|
||||
tokens.push(emitToken(TokenType.CommandName, state));
|
||||
state.skipWhiteSpace();
|
||||
while (!state.isAtEof) {
|
||||
state.next();
|
||||
}
|
||||
// TODO(guillermooo): We need to parse multiple commands.
|
||||
var args = emitToken(TokenType.CommandArgs, state);
|
||||
if (args) {
|
||||
tokens.push(args);
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function lexForwardSearch(state : Scanner, tokens : Token[]): LexFunction {
|
||||
// The first slash has already been lexed.
|
||||
state.skip("/"); // XXX: really?
|
||||
var escaping : boolean;
|
||||
var searchTerm = "";
|
||||
while (!state.isAtEof) {
|
||||
var c = state.next();
|
||||
if (c === "/" && !escaping) {
|
||||
break;
|
||||
}
|
||||
if (c === "\\") {
|
||||
escaping = true;
|
||||
continue;
|
||||
} else {
|
||||
escaping = false;
|
||||
}
|
||||
searchTerm += c !== "\\" ? c : "\\\\";
|
||||
}
|
||||
tokens.push(new Token(TokenType.ForwardSearch, searchTerm));
|
||||
state.ignore();
|
||||
if (!state.isAtEof) {
|
||||
state.skip("/");
|
||||
};
|
||||
return lexRange;
|
||||
}
|
||||
function lexForwardSearch(state : Scanner, tokens : Token[]): LexFunction {
|
||||
// The first slash has already been lexed.
|
||||
state.skip("/"); // XXX: really?
|
||||
var escaping : boolean;
|
||||
var searchTerm = "";
|
||||
while (!state.isAtEof) {
|
||||
var c = state.next();
|
||||
if (c === "/" && !escaping) {
|
||||
break;
|
||||
}
|
||||
if (c === "\\") {
|
||||
escaping = true;
|
||||
continue;
|
||||
} else {
|
||||
escaping = false;
|
||||
}
|
||||
searchTerm += c !== "\\" ? c : "\\\\";
|
||||
}
|
||||
tokens.push(new Token(TokenType.ForwardSearch, searchTerm));
|
||||
state.ignore();
|
||||
if (!state.isAtEof) {
|
||||
state.skip("/");
|
||||
};
|
||||
return lexRange;
|
||||
}
|
||||
|
||||
function lexReverseSearch(state : Scanner, tokens : Token[]): LexFunction {
|
||||
// The first question mark has already been lexed.
|
||||
state.skip("?"); // XXX: really?
|
||||
var escaping : boolean;
|
||||
var searchTerm = "";
|
||||
while (!state.isAtEof) {
|
||||
var c = state.next();
|
||||
if (c === "?" && !escaping) {
|
||||
break;
|
||||
}
|
||||
if (c === "\\") {
|
||||
escaping = true;
|
||||
continue;
|
||||
} else {
|
||||
escaping = false;
|
||||
}
|
||||
searchTerm += c !== "\\" ? c : "\\\\";
|
||||
}
|
||||
tokens.push(new Token(TokenType.ReverseSearch, searchTerm));
|
||||
state.ignore();
|
||||
if (!state.isAtEof) {
|
||||
state.skip("?");
|
||||
}
|
||||
return lexRange;
|
||||
}
|
||||
function lexReverseSearch(state : Scanner, tokens : Token[]): LexFunction {
|
||||
// The first question mark has already been lexed.
|
||||
state.skip("?"); // XXX: really?
|
||||
var escaping : boolean;
|
||||
var searchTerm = "";
|
||||
while (!state.isAtEof) {
|
||||
var c = state.next();
|
||||
if (c === "?" && !escaping) {
|
||||
break;
|
||||
}
|
||||
if (c === "\\") {
|
||||
escaping = true;
|
||||
continue;
|
||||
} else {
|
||||
escaping = false;
|
||||
}
|
||||
searchTerm += c !== "\\" ? c : "\\\\";
|
||||
}
|
||||
tokens.push(new Token(TokenType.ReverseSearch, searchTerm));
|
||||
state.ignore();
|
||||
if (!state.isAtEof) {
|
||||
state.skip("?");
|
||||
}
|
||||
return lexRange;
|
||||
}
|
||||
}
|
||||
|
@ -4,27 +4,27 @@ import * as util from "../util";
|
||||
|
||||
// Shows the vim command line.
|
||||
export function showCmdLine(initialText = "") {
|
||||
const options : vscode.InputBoxOptions = {
|
||||
prompt: "Vim command line",
|
||||
value: initialText
|
||||
};
|
||||
vscode.window.showInputBox(options).then(
|
||||
runCmdLine,
|
||||
vscode.window.showErrorMessage
|
||||
);
|
||||
const options : vscode.InputBoxOptions = {
|
||||
prompt: "Vim command line",
|
||||
value: initialText
|
||||
};
|
||||
vscode.window.showInputBox(options).then(
|
||||
runCmdLine,
|
||||
vscode.window.showErrorMessage
|
||||
);
|
||||
}
|
||||
|
||||
function runCmdLine(s : string) : void {
|
||||
try {
|
||||
var cmd = parser.parse(s);
|
||||
} catch (e) {
|
||||
util.showInfo(e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var cmd = parser.parse(s);
|
||||
} catch (e) {
|
||||
util.showInfo(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd.isEmpty) {
|
||||
vscode.window.showInformationMessage("empty cmdline");
|
||||
} else {
|
||||
cmd.runOn(vscode.window.activeTextEditor);
|
||||
}
|
||||
if (cmd.isEmpty) {
|
||||
vscode.window.showInformationMessage("empty cmdline");
|
||||
} else {
|
||||
cmd.runOn(vscode.window.activeTextEditor);
|
||||
}
|
||||
}
|
||||
|
@ -3,101 +3,101 @@ import * as token from "./token";
|
||||
export * from "./command_node";
|
||||
|
||||
export class LineRange {
|
||||
left : token.Token[];
|
||||
separator : token.Token;
|
||||
right : token.Token[];
|
||||
left : token.Token[];
|
||||
separator : token.Token;
|
||||
right : token.Token[];
|
||||
|
||||
constructor() {
|
||||
this.left = [];
|
||||
this.right = [];
|
||||
}
|
||||
constructor() {
|
||||
this.left = [];
|
||||
this.right = [];
|
||||
}
|
||||
|
||||
addToken(tok : token.Token) : void {
|
||||
if (tok.type === token.TokenType.Comma) {
|
||||
this.separator = tok;
|
||||
return;
|
||||
}
|
||||
addToken(tok : token.Token) : void {
|
||||
if (tok.type === token.TokenType.Comma) {
|
||||
this.separator = tok;
|
||||
return;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
this.right.push(tok);
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
this.right.push(tok);
|
||||
}
|
||||
}
|
||||
|
||||
get isEmpty() : boolean {
|
||||
return this.left.length === 0 && this.right.length === 0 && !this.separator;
|
||||
}
|
||||
get isEmpty() : boolean {
|
||||
return this.left.length === 0 && this.right.length === 0 && !this.separator;
|
||||
}
|
||||
|
||||
toString() : string {
|
||||
return this.left.toString() + this.separator.content + this.right.toString();
|
||||
}
|
||||
toString() : string {
|
||||
return this.left.toString() + this.separator.content + this.right.toString();
|
||||
}
|
||||
|
||||
runOn(document : vscode.TextEditor) : void {
|
||||
if (this.isEmpty) {
|
||||
return;
|
||||
}
|
||||
var lineRef = this.right.length === 0 ? this.left : this.right;
|
||||
var pos = this.lineRefToPosition(document, lineRef);
|
||||
document.selection = new vscode.Selection(pos, pos);
|
||||
}
|
||||
runOn(document : vscode.TextEditor) : void {
|
||||
if (this.isEmpty) {
|
||||
return;
|
||||
}
|
||||
var lineRef = this.right.length === 0 ? this.left : this.right;
|
||||
var pos = this.lineRefToPosition(document, lineRef);
|
||||
document.selection = new vscode.Selection(pos, pos);
|
||||
}
|
||||
|
||||
lineRefToPosition(doc : vscode.TextEditor, toks : token.Token[]) : vscode.Position {
|
||||
var first = toks[0];
|
||||
switch (first.type) {
|
||||
case token.TokenType.Dollar:
|
||||
case token.TokenType.Percent:
|
||||
return new vscode.Position(doc.document.lineCount, 0);
|
||||
case token.TokenType.Dot:
|
||||
return new vscode.Position(doc.selection.active.line, 0);
|
||||
case token.TokenType.LineNumber:
|
||||
var line = Number.parseInt(first.content);
|
||||
line = Math.max(0, line - 1);
|
||||
line = Math.min(doc.document.lineCount, line);
|
||||
return new vscode.Position(line, 0);
|
||||
default:
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
}
|
||||
lineRefToPosition(doc : vscode.TextEditor, toks : token.Token[]) : vscode.Position {
|
||||
var first = toks[0];
|
||||
switch (first.type) {
|
||||
case token.TokenType.Dollar:
|
||||
case token.TokenType.Percent:
|
||||
return new vscode.Position(doc.document.lineCount, 0);
|
||||
case token.TokenType.Dot:
|
||||
return new vscode.Position(doc.selection.active.line, 0);
|
||||
case token.TokenType.LineNumber:
|
||||
var line = Number.parseInt(first.content);
|
||||
line = Math.max(0, line - 1);
|
||||
line = Math.min(doc.document.lineCount, line);
|
||||
return new vscode.Position(line, 0);
|
||||
default:
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CommandLine {
|
||||
range : LineRange;
|
||||
command : CommandBase;
|
||||
range : LineRange;
|
||||
command : CommandBase;
|
||||
|
||||
constructor() {
|
||||
this.range = new LineRange();
|
||||
}
|
||||
constructor() {
|
||||
this.range = new LineRange();
|
||||
}
|
||||
|
||||
get isEmpty() : boolean {
|
||||
return this.range.isEmpty && !this.command;
|
||||
}
|
||||
get isEmpty() : boolean {
|
||||
return this.range.isEmpty && !this.command;
|
||||
}
|
||||
|
||||
toString() : string {
|
||||
return ":" + this.range.toString() + " " + this.command.toString();
|
||||
}
|
||||
toString() : string {
|
||||
return ":" + this.range.toString() + " " + this.command.toString();
|
||||
}
|
||||
|
||||
runOn(document : vscode.TextEditor) : void {
|
||||
if (!this.command) {
|
||||
this.range.runOn(document);
|
||||
return;
|
||||
}
|
||||
runOn(document : vscode.TextEditor) : void {
|
||||
if (!this.command) {
|
||||
this.range.runOn(document);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: calc range
|
||||
this.command.runOn(document);
|
||||
}
|
||||
// TODO: calc range
|
||||
this.command.runOn(document);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommandBase {
|
||||
name : string;
|
||||
shortName : string;
|
||||
runOn(textEditor : vscode.TextEditor) : void;
|
||||
name : string;
|
||||
shortName : string;
|
||||
runOn(textEditor : vscode.TextEditor) : void;
|
||||
}
|
||||
|
@ -1,102 +1,102 @@
|
||||
import * as token from "./token";
|
||||
import * as node from "./node";
|
||||
import * as lexer from "./lexer";
|
||||
import {commandParsers} from "./subparsers";
|
||||
import * as token from './token';
|
||||
import * as node from './node';
|
||||
import * as lexer from './lexer';
|
||||
import {commandParsers} from './subparser';
|
||||
|
||||
interface ParseFunction {
|
||||
(state : ParserState, command : node.CommandLine) : ParseFunction;
|
||||
(state : ParserState, command : node.CommandLine) : ParseFunction;
|
||||
}
|
||||
|
||||
export function parse(input : string) : node.CommandLine {
|
||||
var cmd = new node.CommandLine();
|
||||
var f : ParseFunction = parseLineRange;
|
||||
let state : ParserState = new ParserState(input);
|
||||
while (f) {
|
||||
f = f(state, cmd);
|
||||
}
|
||||
return cmd;
|
||||
var cmd = new node.CommandLine();
|
||||
var f : ParseFunction = parseLineRange;
|
||||
let state : ParserState = new ParserState(input);
|
||||
while (f) {
|
||||
f = f(state, cmd);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
function parseLineRange(state : ParserState, commandLine : node.CommandLine) : ParseFunction {
|
||||
while (true) {
|
||||
let tok = state.next();
|
||||
switch (tok.type) {
|
||||
case token.TokenType.Eof:
|
||||
return null;
|
||||
case token.TokenType.Dot:
|
||||
case token.TokenType.Dollar:
|
||||
case token.TokenType.Percent:
|
||||
case token.TokenType.Comma:
|
||||
case token.TokenType.LineNumber:
|
||||
commandLine.range.addToken(tok);
|
||||
continue;
|
||||
case token.TokenType.CommandName:
|
||||
state.backup();
|
||||
return parseCommand;
|
||||
// commandLine.command = new node.CommandLineCommand(tok.content, null);
|
||||
// continue;
|
||||
default:
|
||||
console.warn("skipping token " + "Token(" + tok.type + ",{" + tok.content + "})");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
let tok = state.next();
|
||||
switch (tok.type) {
|
||||
case token.TokenType.Eof:
|
||||
return null;
|
||||
case token.TokenType.Dot:
|
||||
case token.TokenType.Dollar:
|
||||
case token.TokenType.Percent:
|
||||
case token.TokenType.Comma:
|
||||
case token.TokenType.LineNumber:
|
||||
commandLine.range.addToken(tok);
|
||||
continue;
|
||||
case token.TokenType.CommandName:
|
||||
state.backup();
|
||||
return parseCommand;
|
||||
// commandLine.command = new node.CommandLineCommand(tok.content, null);
|
||||
// continue;
|
||||
default:
|
||||
console.warn("skipping token " + "Token(" + tok.type + ",{" + tok.content + "})");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseCommand(state : ParserState, commandLine : node.CommandLine) : ParseFunction {
|
||||
while (!state.isAtEof) {
|
||||
var tok = state.next();
|
||||
switch (tok.type) {
|
||||
case token.TokenType.CommandName:
|
||||
var commandParser = commandParsers[tok.content];
|
||||
if (!commandParser) {
|
||||
throw new Error("not implemented or not a valid command");
|
||||
}
|
||||
// TODO: Pass the args, but keep in mind there could be multiple
|
||||
// commands, not just one.
|
||||
var argsTok = state.next();
|
||||
var args = argsTok.type === token.TokenType.CommandArgs ? argsTok.content : null;
|
||||
commandLine.command = commandParser(args);
|
||||
return null;
|
||||
default:
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
}
|
||||
if (!state.isAtEof) {
|
||||
state.backup();
|
||||
return parseCommand;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
while (!state.isAtEof) {
|
||||
var tok = state.next();
|
||||
switch (tok.type) {
|
||||
case token.TokenType.CommandName:
|
||||
var commandParser = commandParsers[tok.content];
|
||||
if (!commandParser) {
|
||||
throw new Error("not implemented or not a valid command");
|
||||
}
|
||||
// TODO: Pass the args, but keep in mind there could be multiple
|
||||
// commands, not just one.
|
||||
var argsTok = state.next();
|
||||
var args = argsTok.type === token.TokenType.CommandArgs ? argsTok.content : null;
|
||||
commandLine.command = commandParser(args);
|
||||
return null;
|
||||
default:
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
}
|
||||
if (!state.isAtEof) {
|
||||
state.backup();
|
||||
return parseCommand;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Keeps track of parsing state.
|
||||
class ParserState {
|
||||
tokens : token.Token[] = [];
|
||||
pos : number = 0;
|
||||
tokens : token.Token[] = [];
|
||||
pos : number = 0;
|
||||
|
||||
constructor(input : string) {
|
||||
this.lex(input);
|
||||
}
|
||||
constructor(input : string) {
|
||||
this.lex(input);
|
||||
}
|
||||
|
||||
lex(input : string) {
|
||||
this.tokens = lexer.lex(input);
|
||||
}
|
||||
lex(input : string) {
|
||||
this.tokens = lexer.lex(input);
|
||||
}
|
||||
|
||||
next() : token.Token {
|
||||
if (this.pos >= this.tokens.length) {
|
||||
this.pos = this.tokens.length;
|
||||
return new token.Token(token.TokenType.Eof, "__EOF__");
|
||||
}
|
||||
let tok = this.tokens[this.pos];
|
||||
this.pos++;
|
||||
return tok;
|
||||
}
|
||||
next() : token.Token {
|
||||
if (this.pos >= this.tokens.length) {
|
||||
this.pos = this.tokens.length;
|
||||
return new token.Token(token.TokenType.Eof, '__EOF__');
|
||||
}
|
||||
let tok = this.tokens[this.pos];
|
||||
this.pos++;
|
||||
return tok;
|
||||
}
|
||||
|
||||
backup() : void {
|
||||
this.pos--;
|
||||
}
|
||||
backup() : void {
|
||||
this.pos--;
|
||||
}
|
||||
|
||||
get isAtEof() {
|
||||
return this.pos >= this.tokens.length; // XXX the last token is TokenEof; is this correct?
|
||||
}
|
||||
get isAtEof() {
|
||||
return this.pos >= this.tokens.length;
|
||||
}
|
||||
}
|
||||
|
@ -1,88 +1,106 @@
|
||||
|
||||
// Provides state and behavior to scan an input string character by character.
|
||||
export class Scanner {
|
||||
static EOF : string = "__EOF__";
|
||||
start : number = 0;
|
||||
pos : number = 0;
|
||||
input : string;
|
||||
static EOF : string = "__EOF__";
|
||||
start : number = 0;
|
||||
pos : number = 0;
|
||||
input : string;
|
||||
|
||||
constructor(input : string) {
|
||||
this.input = input;
|
||||
}
|
||||
constructor(input : string) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
// Returns the next character in the input, or EOF.
|
||||
next() : string {
|
||||
if (this.isAtEof) {
|
||||
this.pos = this.input.length;
|
||||
return Scanner.EOF;
|
||||
}
|
||||
let c = this.input[this.pos];
|
||||
this.pos++;
|
||||
return c;
|
||||
}
|
||||
// Returns the next character in the input, or EOF.
|
||||
next() : string {
|
||||
if (this.isAtEof) {
|
||||
this.pos = this.input.length;
|
||||
return Scanner.EOF;
|
||||
}
|
||||
let c = this.input[this.pos];
|
||||
this.pos++;
|
||||
return c;
|
||||
}
|
||||
|
||||
// Returns whether we've reached EOF.
|
||||
get isAtEof() : boolean {
|
||||
return this.pos >= this.input.length;
|
||||
}
|
||||
// Returns whether we've reached EOF.
|
||||
get isAtEof() : boolean {
|
||||
return this.pos >= this.input.length;
|
||||
}
|
||||
|
||||
// Ignores the span of text between the current start and the current position.
|
||||
ignore() : void {
|
||||
this.start = this.pos;
|
||||
}
|
||||
// Ignores the span of text between the current start and the current position.
|
||||
ignore() : void {
|
||||
this.start = this.pos;
|
||||
}
|
||||
|
||||
// Returns the span of text between the current start and the current position.
|
||||
emit() : string {
|
||||
let s = this.input.substring(this.start, this.pos);
|
||||
this.ignore();
|
||||
return s;
|
||||
}
|
||||
// Returns the span of text between the current start and the current position.
|
||||
emit() : string {
|
||||
let s = this.input.substring(this.start, this.pos);
|
||||
this.ignore();
|
||||
return s;
|
||||
}
|
||||
|
||||
backup(): void {
|
||||
this.pos--;
|
||||
}
|
||||
backup(): void {
|
||||
this.pos--;
|
||||
}
|
||||
|
||||
// skips over c and ignores the text span
|
||||
skip(c : string) : void {
|
||||
var s = this.next();
|
||||
while (!this.isAtEof) {
|
||||
if (s !== c) {
|
||||
break;
|
||||
}
|
||||
s = this.next();
|
||||
}
|
||||
if (!this.isAtEof) {
|
||||
this.backup();
|
||||
}
|
||||
this.ignore();
|
||||
}
|
||||
// skips over c and ignores the text span
|
||||
skip(c : string) : void {
|
||||
if (this.isAtEof) {
|
||||
return;
|
||||
}
|
||||
var s = this.next();
|
||||
while (!this.isAtEof) {
|
||||
if (s !== c) {
|
||||
break;
|
||||
}
|
||||
s = this.next();
|
||||
}
|
||||
this.backup();
|
||||
this.ignore();
|
||||
}
|
||||
|
||||
// skips text while any of chars matches and ignores the text span
|
||||
skipRun(...chars : string[]) : void {
|
||||
while (!this.isAtEof) {
|
||||
var c = this.next();
|
||||
if (chars.indexOf(c) === -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!this.isAtEof) {
|
||||
this.backup();
|
||||
}
|
||||
this.ignore();
|
||||
}
|
||||
// skips text while any of chars matches and ignores the text span
|
||||
skipRun(...chars : string[]) : void {
|
||||
if (this.isAtEof) {
|
||||
return;
|
||||
}
|
||||
while (!this.isAtEof) {
|
||||
var c = this.next();
|
||||
if (chars.indexOf(c) === -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.backup();
|
||||
this.ignore();
|
||||
}
|
||||
|
||||
// skips over whitespace (tab, space) and ignores the text span
|
||||
skipWhiteSpace(): void {
|
||||
while (true) {
|
||||
var c = this.next();
|
||||
if (c === " " || c === "\t") {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!this.isAtEof) {
|
||||
this.backup();
|
||||
}
|
||||
this.ignore();
|
||||
}
|
||||
// skips over whitespace (tab, space) and ignores the text span
|
||||
skipWhiteSpace(): void {
|
||||
if (this.isAtEof) {
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
var c = this.next();
|
||||
if (c === " " || c === "\t") {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.backup();
|
||||
this.ignore();
|
||||
}
|
||||
|
||||
expect(value : string) : void {
|
||||
if (!this.input.substring(this.pos).startsWith(value)) {
|
||||
throw new Error("Unexpected character.");
|
||||
}
|
||||
this.pos += value.length;
|
||||
}
|
||||
|
||||
expectOneOf(...values : string[]) : void {
|
||||
let match = values.filter(s => this.input.substr(this.pos).startsWith(s));
|
||||
if (match.length !== 1) {
|
||||
throw new Error("Unexpected character.");
|
||||
}
|
||||
this.pos += match[0].length;
|
||||
}
|
||||
}
|
||||
|
68
src/cmd_line/subparser.ts
Normal file
68
src/cmd_line/subparser.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import * as node from "./node";
|
||||
import * as command_node from './command_node';
|
||||
import {Scanner} from './scanner';
|
||||
|
||||
// maps command names to parsers for said commands.
|
||||
export const commandParsers = {
|
||||
w: parseWriteCommandArgs,
|
||||
write: parseWriteCommandArgs
|
||||
};
|
||||
|
||||
function parseWriteCommandArgs(args : string) : node.WriteCommand {
|
||||
if (!args) {
|
||||
return new node.WriteCommand();
|
||||
}
|
||||
var scannedArgs : command_node.WriteCommandArguments = {};
|
||||
var scanner = new Scanner(args);
|
||||
while (true) {
|
||||
scanner.skipWhiteSpace();
|
||||
if (scanner.isAtEof) {
|
||||
break;
|
||||
}
|
||||
let c = scanner.next();
|
||||
switch (c) {
|
||||
case '!':
|
||||
if (scanner.start > 0) {
|
||||
// :write !cmd
|
||||
scanner.ignore();
|
||||
while (!scanner.isAtEof) {
|
||||
scanner.next();
|
||||
}
|
||||
// vim ignores silently if no command after :w !
|
||||
scannedArgs.cmd = scanner.emit().trim() || undefined;
|
||||
continue;
|
||||
}
|
||||
// :write!
|
||||
scannedArgs.bang = true;
|
||||
scanner.ignore();
|
||||
continue;
|
||||
case '+':
|
||||
// :write ++opt=value
|
||||
scanner.expect('+');
|
||||
scanner.ignore();
|
||||
scanner.expectOneOf('bin', 'nobin', 'ff', 'enc');
|
||||
scannedArgs.opt = scanner.emit();
|
||||
scanner.expect('=');
|
||||
scanner.ignore();
|
||||
while (!scanner.isAtEof) {
|
||||
let c = scanner.next();
|
||||
if (c !== ' ' || c !== '\t') {
|
||||
continue;
|
||||
}
|
||||
scanner.backup();
|
||||
continue;
|
||||
}
|
||||
let value = scanner.emit();
|
||||
if (!value) {
|
||||
throw new Error("Expected value for option.");
|
||||
}
|
||||
scannedArgs.optValue = value;
|
||||
continue;
|
||||
default:
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
// TODO: actually parse arguments.
|
||||
// ++bin ++nobin ++ff ++enc =VALUE
|
||||
return new node.WriteCommand(scannedArgs);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import * as node from "./node";
|
||||
|
||||
// maps command names to parsers for said commands.
|
||||
export const commandParsers = {
|
||||
w: parseWriteCommandArgs,
|
||||
write: parseWriteCommandArgs
|
||||
};
|
||||
|
||||
export function parseWriteCommandArgs(args : string = null) {
|
||||
// TODO: actually parse arguments.
|
||||
return new node.WriteCommand(args ? args : null);
|
||||
}
|
@ -1,28 +1,28 @@
|
||||
// Tokens for the Vim command line.
|
||||
|
||||
export enum TokenType {
|
||||
Unknown,
|
||||
Eof,
|
||||
LineNumber,
|
||||
Dot,
|
||||
Dollar,
|
||||
Percent,
|
||||
Comma,
|
||||
Plus,
|
||||
Minus,
|
||||
CommandName,
|
||||
CommandArgs,
|
||||
ForwardSearch,
|
||||
ReverseSearch,
|
||||
Offset
|
||||
Unknown,
|
||||
Eof,
|
||||
LineNumber,
|
||||
Dot,
|
||||
Dollar,
|
||||
Percent,
|
||||
Comma,
|
||||
Plus,
|
||||
Minus,
|
||||
CommandName,
|
||||
CommandArgs,
|
||||
ForwardSearch,
|
||||
ReverseSearch,
|
||||
Offset
|
||||
}
|
||||
|
||||
export class Token {
|
||||
type : TokenType;
|
||||
content : string;
|
||||
type : TokenType;
|
||||
content : string;
|
||||
|
||||
constructor(type : TokenType, content : string) {
|
||||
this.type = type;
|
||||
this.content = content;
|
||||
}
|
||||
constructor(type : TokenType, content : string) {
|
||||
this.type = type;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ var testRunner = require('vscode/lib/testrunner');
|
||||
// You can directly control Mocha options by uncommenting the following lines
|
||||
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
|
||||
testRunner.configure({
|
||||
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
|
||||
useColors: true // colored output from test results
|
||||
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
|
||||
useColors: true // colored output from test results
|
||||
});
|
||||
|
||||
module.exports = testRunner;
|
||||
|
@ -5,92 +5,92 @@ import {Token, TokenType} from '../src/cmd_line/token'
|
||||
|
||||
suite("Cmd line tests - lexing", () => {
|
||||
|
||||
test("can lex empty string", () => {
|
||||
var tokens = lexer.lex("");
|
||||
assert.equal(tokens.length, 0);
|
||||
});
|
||||
test("can lex empty string", () => {
|
||||
var tokens = lexer.lex("");
|
||||
assert.equal(tokens.length, 0);
|
||||
});
|
||||
|
||||
test("can lex comma", () => {
|
||||
var tokens = lexer.lex(",");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Comma, ',').content);
|
||||
});
|
||||
test("can lex comma", () => {
|
||||
var tokens = lexer.lex(",");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Comma, ',').content);
|
||||
});
|
||||
|
||||
test("can lex percent", () => {
|
||||
var tokens = lexer.lex("%");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Percent, '%').content);
|
||||
});
|
||||
test("can lex percent", () => {
|
||||
var tokens = lexer.lex("%");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Percent, '%').content);
|
||||
});
|
||||
|
||||
test("can lex dollar", () => {
|
||||
var tokens = lexer.lex("$");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Dollar, '$').content);
|
||||
});
|
||||
test("can lex dollar", () => {
|
||||
var tokens = lexer.lex("$");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Dollar, '$').content);
|
||||
});
|
||||
|
||||
test("can lex dot", () => {
|
||||
var tokens = lexer.lex(".");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Dot, '.').content);
|
||||
});
|
||||
test("can lex dot", () => {
|
||||
var tokens = lexer.lex(".");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Dot, '.').content);
|
||||
});
|
||||
|
||||
test("can lex one number", () => {
|
||||
var tokens = lexer.lex("1");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.LineNumber, "1").content);
|
||||
});
|
||||
test("can lex one number", () => {
|
||||
var tokens = lexer.lex("1");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.LineNumber, "1").content);
|
||||
});
|
||||
|
||||
test("can lex longer number", () => {
|
||||
var tokens = lexer.lex("100");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.LineNumber, "100").content);
|
||||
});
|
||||
test("can lex longer number", () => {
|
||||
var tokens = lexer.lex("100");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.LineNumber, "100").content);
|
||||
});
|
||||
|
||||
test("can lex plus", () => {
|
||||
var tokens = lexer.lex("+");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Plus, '+').content);
|
||||
});
|
||||
test("can lex plus", () => {
|
||||
var tokens = lexer.lex("+");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Plus, '+').content);
|
||||
});
|
||||
|
||||
test("can lex minus", () => {
|
||||
var tokens = lexer.lex("-");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Minus, '-').content);
|
||||
});
|
||||
test("can lex minus", () => {
|
||||
var tokens = lexer.lex("-");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.Minus, '-').content);
|
||||
});
|
||||
|
||||
test("can lex forward search", () => {
|
||||
var tokens = lexer.lex("/horses/");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.ForwardSearch, "horses").content);
|
||||
});
|
||||
test("can lex forward search", () => {
|
||||
var tokens = lexer.lex("/horses/");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.ForwardSearch, "horses").content);
|
||||
});
|
||||
|
||||
test("can lex forward search escaping", () => {
|
||||
var tokens = lexer.lex("/hor\\/ses/");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.ForwardSearch, "hor/ses").content);
|
||||
});
|
||||
test("can lex forward search escaping", () => {
|
||||
var tokens = lexer.lex("/hor\\/ses/");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.ForwardSearch, "hor/ses").content);
|
||||
});
|
||||
|
||||
test("can lex reverse search", () => {
|
||||
var tokens = lexer.lex("?worms?");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.ReverseSearch, "worms").content);
|
||||
});
|
||||
test("can lex reverse search", () => {
|
||||
var tokens = lexer.lex("?worms?");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.ReverseSearch, "worms").content);
|
||||
});
|
||||
|
||||
test("can lex reverse search escaping", () => {
|
||||
var tokens = lexer.lex("?wor\\?ms?");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.ReverseSearch, "wor?ms").content);
|
||||
});
|
||||
test("can lex reverse search escaping", () => {
|
||||
var tokens = lexer.lex("?wor\\?ms?");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.ReverseSearch, "wor?ms").content);
|
||||
});
|
||||
|
||||
test("can lex command name", () => {
|
||||
var tokens = lexer.lex("w");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.CommandName, "w").content);
|
||||
});
|
||||
test("can lex command name", () => {
|
||||
var tokens = lexer.lex("w");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.CommandName, "w").content);
|
||||
});
|
||||
|
||||
test("can lex command args", () => {
|
||||
var tokens = lexer.lex("w something");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.CommandName, "w").content);
|
||||
assert.equal(tokens[1].content, new Token(TokenType.CommandArgs, "something").content);
|
||||
});
|
||||
test("can lex command args", () => {
|
||||
var tokens = lexer.lex("w something");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.CommandName, "w").content);
|
||||
assert.equal(tokens[1].content, new Token(TokenType.CommandArgs, "something").content);
|
||||
});
|
||||
|
||||
test("can lex long command name and args", () => {
|
||||
var tokens = lexer.lex("write12 something here");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.CommandName, "write").content);
|
||||
assert.equal(tokens[1].content, new Token(TokenType.CommandArgs, "12 something here").content);
|
||||
});
|
||||
test("can lex long command name and args", () => {
|
||||
var tokens = lexer.lex("write12 something here");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.CommandName, "write").content);
|
||||
assert.equal(tokens[1].content, new Token(TokenType.CommandArgs, "12 something here").content);
|
||||
});
|
||||
|
||||
test("can lex left and right line refs", () => {
|
||||
var tokens = lexer.lex("20,30");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.LineNumber, "20").content);
|
||||
assert.equal(tokens[1].content, new Token(TokenType.LineNumber, ",").content);
|
||||
assert.equal(tokens[2].content, new Token(TokenType.LineNumber, "30").content);
|
||||
});
|
||||
});
|
||||
test("can lex left and right line refs", () => {
|
||||
var tokens = lexer.lex("20,30");
|
||||
assert.equal(tokens[0].content, new Token(TokenType.LineNumber, "20").content);
|
||||
assert.equal(tokens[1].content, new Token(TokenType.LineNumber, ",").content);
|
||||
assert.equal(tokens[2].content, new Token(TokenType.LineNumber, "30").content);
|
||||
});
|
||||
});
|
||||
|
@ -6,36 +6,36 @@ import * as token from '../src/cmd_line/token';
|
||||
|
||||
suite("Cmd line tests - parser", () => {
|
||||
|
||||
test("can parse empty string", () => {
|
||||
var cmd = parser.parse("");
|
||||
assert.ok(cmd.isEmpty);
|
||||
});
|
||||
test("can parse empty string", () => {
|
||||
var cmd = parser.parse("");
|
||||
assert.ok(cmd.isEmpty);
|
||||
});
|
||||
|
||||
// TODO: Range tests follow -- should prolly create a suite for this
|
||||
test("can parse left - dot", () => {
|
||||
var cmd : node.CommandLine = parser.parse(".");
|
||||
assert.equal(cmd.range.left[0].type, token.TokenType.Dot);
|
||||
});
|
||||
// TODO: Range tests follow -- should prolly create a suite for this
|
||||
test("can parse left - dot", () => {
|
||||
var cmd : node.CommandLine = parser.parse(".");
|
||||
assert.equal(cmd.range.left[0].type, token.TokenType.Dot);
|
||||
});
|
||||
|
||||
test("can parse left - dollar", () => {
|
||||
var cmd : node.CommandLine = parser.parse("$");
|
||||
assert.equal(cmd.range.left[0].type, token.TokenType.Dollar);
|
||||
});
|
||||
test("can parse left - dollar", () => {
|
||||
var cmd : node.CommandLine = parser.parse("$");
|
||||
assert.equal(cmd.range.left[0].type, token.TokenType.Dollar);
|
||||
});
|
||||
|
||||
test("can parse left - percent", () => {
|
||||
var cmd : node.CommandLine = parser.parse("%");
|
||||
assert.equal(cmd.range.left[0].type, token.TokenType.Percent);
|
||||
});
|
||||
test("can parse left - percent", () => {
|
||||
var cmd : node.CommandLine = parser.parse("%");
|
||||
assert.equal(cmd.range.left[0].type, token.TokenType.Percent);
|
||||
});
|
||||
|
||||
test("can parse separator - comma", () => {
|
||||
var cmd : node.CommandLine = parser.parse(",");
|
||||
assert.equal(cmd.range.separator.type, token.TokenType.Comma);
|
||||
});
|
||||
test("can parse separator - comma", () => {
|
||||
var cmd : node.CommandLine = parser.parse(",");
|
||||
assert.equal(cmd.range.separator.type, token.TokenType.Comma);
|
||||
});
|
||||
|
||||
test("can parse right - dollar", () => {
|
||||
var cmd : node.CommandLine = parser.parse(",$");
|
||||
assert.equal(cmd.range.left.length, 0);
|
||||
assert.equal(cmd.range.right.length, 1);
|
||||
assert.equal(cmd.range.right[0].type, token.TokenType.Dollar, "unexpected token");
|
||||
});
|
||||
test("can parse right - dollar", () => {
|
||||
var cmd : node.CommandLine = parser.parse(",$");
|
||||
assert.equal(cmd.range.left.length, 0);
|
||||
assert.equal(cmd.range.right.length, 1);
|
||||
assert.equal(cmd.range.right[0].type, token.TokenType.Dollar, "unexpected token");
|
||||
});
|
||||
});
|
||||
|
@ -4,54 +4,54 @@ import * as lexerState from '../src/cmd_line/scanner'
|
||||
|
||||
suite("Cmd line tests - lexer state", () => {
|
||||
|
||||
test("can init lexer state", () => {
|
||||
var state = new lexerState.Scanner("dog");
|
||||
assert.equal(state.input, "dog");
|
||||
});
|
||||
|
||||
test("can detect EOF with empty input", () => {
|
||||
var state = new lexerState.Scanner("");
|
||||
assert.ok(state.isAtEof);
|
||||
});
|
||||
test("can init lexer state", () => {
|
||||
var state = new lexerState.Scanner("dog");
|
||||
assert.equal(state.input, "dog");
|
||||
});
|
||||
|
||||
test("can detect EOF with empty input", () => {
|
||||
var state = new lexerState.Scanner("");
|
||||
assert.ok(state.isAtEof);
|
||||
});
|
||||
|
||||
test("next() returns EOF at EOF", () => {
|
||||
var state = new lexerState.Scanner("");
|
||||
assert.equal(state.next(), lexerState.Scanner.EOF);
|
||||
assert.equal(state.next(), lexerState.Scanner.EOF);
|
||||
assert.equal(state.next(), lexerState.Scanner.EOF);
|
||||
});
|
||||
test("next() returns EOF at EOF", () => {
|
||||
var state = new lexerState.Scanner("");
|
||||
assert.equal(state.next(), lexerState.Scanner.EOF);
|
||||
assert.equal(state.next(), lexerState.Scanner.EOF);
|
||||
assert.equal(state.next(), lexerState.Scanner.EOF);
|
||||
});
|
||||
|
||||
test("next() can scan", () => {
|
||||
var state = new lexerState.Scanner("dog");
|
||||
assert.equal(state.next(), "d");
|
||||
assert.equal(state.next(), "o");
|
||||
assert.equal(state.next(), "g")
|
||||
assert.equal(state.next(), lexerState.Scanner.EOF);
|
||||
});
|
||||
|
||||
test("can emit", () => {
|
||||
var state = new lexerState.Scanner("dog cat");
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
assert.equal(state.emit(), "dog");
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
assert.equal(state.emit(), " cat");
|
||||
});
|
||||
test("next() can scan", () => {
|
||||
var state = new lexerState.Scanner("dog");
|
||||
assert.equal(state.next(), "d");
|
||||
assert.equal(state.next(), "o");
|
||||
assert.equal(state.next(), "g")
|
||||
assert.equal(state.next(), lexerState.Scanner.EOF);
|
||||
});
|
||||
|
||||
test("can emit", () => {
|
||||
var state = new lexerState.Scanner("dog cat");
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
assert.equal(state.emit(), "dog");
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
assert.equal(state.emit(), " cat");
|
||||
});
|
||||
|
||||
test("can ignore", () => {
|
||||
var state = new lexerState.Scanner("dog cat");
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
state.ignore();
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
assert.equal(state.emit(), "cat");
|
||||
});
|
||||
});
|
||||
test("can ignore", () => {
|
||||
var state = new lexerState.Scanner("dog cat");
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
state.ignore();
|
||||
state.next();
|
||||
state.next();
|
||||
state.next();
|
||||
assert.equal(state.emit(), "cat");
|
||||
});
|
||||
});
|
||||
|
79
test/subparser.test.ts
Normal file
79
test/subparser.test.ts
Normal file
@ -0,0 +1,79 @@
|
||||
// The module 'assert' provides assertion methods from node
|
||||
import * as assert from 'assert';
|
||||
|
||||
import {commandParsers} from '../src/cmd_line/subparser';
|
||||
import {WriteCommandArguments} from '../src/cmd_line/command_node';
|
||||
|
||||
suite("subparsers - :write args", () => {
|
||||
|
||||
test("parsers for :write are set up correctly", () => {
|
||||
assert.equal(commandParsers.write.name, commandParsers.w.name);
|
||||
});
|
||||
|
||||
test("can parse empty args", () => {
|
||||
// TODO: perhaps we don't need to export this func at all.
|
||||
// TODO: this func must return args only, not a command?
|
||||
// TODO: the range must be passed separately, not as arg.
|
||||
var args = commandParsers.write("");
|
||||
assert.equal(args.args.append, undefined);
|
||||
assert.equal(args.args.bang, undefined);
|
||||
assert.equal(args.args.cmd, undefined);
|
||||
assert.equal(args.args.file, undefined);
|
||||
assert.equal(args.args.opt, undefined);
|
||||
assert.equal(args.args.optValue, undefined);
|
||||
assert.equal(args.args.range, undefined);
|
||||
});
|
||||
|
||||
test("can parse ++opt", () => {
|
||||
|
||||
var args = commandParsers.write("++enc=foo");
|
||||
assert.equal(args.args.append, undefined);
|
||||
assert.equal(args.args.bang, undefined);
|
||||
assert.equal(args.args.cmd, undefined);
|
||||
assert.equal(args.args.file, undefined);
|
||||
assert.equal(args.args.opt, 'enc');
|
||||
assert.equal(args.args.optValue, 'foo');
|
||||
assert.equal(args.args.range, undefined);
|
||||
});
|
||||
|
||||
test("throws if bad ++opt name", () => {
|
||||
|
||||
assert.throws(() => commandParsers.write("++foo=foo"));
|
||||
});
|
||||
|
||||
test("can parse bang", () => {
|
||||
|
||||
var args = commandParsers.write("!");
|
||||
assert.equal(args.args.append, undefined);
|
||||
assert.equal(args.args.bang, true);
|
||||
assert.equal(args.args.cmd, undefined);
|
||||
assert.equal(args.args.file, undefined);
|
||||
assert.equal(args.args.opt, undefined);
|
||||
assert.equal(args.args.optValue, undefined);
|
||||
assert.equal(args.args.range, undefined);
|
||||
});
|
||||
|
||||
test("can parse ' !cmd'", () => {
|
||||
|
||||
var args = commandParsers.write(" !foo");
|
||||
assert.equal(args.args.append, undefined);
|
||||
assert.equal(args.args.bang, undefined);
|
||||
assert.equal(args.args.cmd, 'foo');
|
||||
assert.equal(args.args.file, undefined);
|
||||
assert.equal(args.args.opt, undefined);
|
||||
assert.equal(args.args.optValue, undefined);
|
||||
assert.equal(args.args.range, undefined);
|
||||
});
|
||||
|
||||
test("can parse ' !cmd' when cmd is empty", () => {
|
||||
|
||||
var args = commandParsers.write(" !");
|
||||
assert.equal(args.args.append, undefined);
|
||||
assert.equal(args.args.bang, undefined);
|
||||
assert.equal(args.args.cmd, undefined);
|
||||
assert.equal(args.args.file, undefined);
|
||||
assert.equal(args.args.opt, undefined);
|
||||
assert.equal(args.args.optValue, undefined);
|
||||
assert.equal(args.args.range, undefined);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user