implement :w!

This commit is contained in:
guillermooo 2015-11-19 22:26:32 +01:00
parent 3e151bfb16
commit a765d5a061
14 changed files with 824 additions and 620 deletions

View File

@ -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);
}
});
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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
View 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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
});
});

View File

@ -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");
});
});

View File

@ -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
View 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);
});
});