Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# http://editorconfig.org

root = true

[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
trim_trailing_whitespace = true
14 changes: 14 additions & 0 deletions locale/lang.en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"info": {
"initializing": "Initializing REPL extension...",
"cwd": "[%s] working at:%s",
"starting": "[%s] starting to interpret %d bytes of code:",
"disposing": "Disposing REPL server..."
},
"warn": {
"CRUD": " Warning: Be careful with CRUD operations since the code is running multiple times in REPL."
},
"error": {
"notJavascript": "[Node.js REPL] Selected document is not Javascript, unable to start REPL here."
}
}
14 changes: 14 additions & 0 deletions locale/lang.zh-cn.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"info": {
"initializing": "初始化 REPL 服务端中……",
"cwd": "[%s] 工作目录为:%s",
"starting": "[%s] 开始解释长度为 %d 字节的代码:",
"disposing": "销毁 REPL 服务端中……"
},
"warn": {
"CRUD": " 警告: 注意对文件、数据库等等的增删查改操作,因为代码会在 REPL 中执行多次。"
},
"error": {
"notJavascript": "[Node.js REPL] 选中的文档不是 Javascript 文件,不能在此执行 REPL。"
}
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@
"contributes": {
"commands": [{
"command": "extension.nodejsRepl",
"title": "Node.js Interactive window (REPL)"
"title": "%command.extension.nodejsRepl%"
}, {
"command": "extension.nodejsReplCurrent",
"title": "Node.js Interactive window (REPL) at selected file"
"title": "%command.extension.nodejsReplCurrent%"
}, {
"command": "extension.nodejsReplClose",
"title": "Stop Node.js Interactive window (REPL)"
"title": "%command.extension.nodejsReplClose%"
}]
},
"scripts": {
Expand Down
5 changes: 5 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"command.extension.nodejsRepl": "Node.js REPL: Start REPL at New Untitled File",
"command.extension.nodejsReplCurrent": "Node.js REPL: Start REPL at Current File",
"command.extension.nodejsReplClose": "Node.js REPL: Stop REPL"
}
5 changes: 5 additions & 0 deletions package.nls.zh-cn.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"command.extension.nodejsRepl": "Node.js REPL: 在新建无标题文件运行交互式解释器",
"command.extension.nodejsReplCurrent": "Node.js REPL: 在当前编辑器运行交互式解释器",
"command.extension.nodejsReplClose": "Node.js REPL: 停止交互式解释器"
}
60 changes: 60 additions & 0 deletions src/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as Path from 'path';
import * as Fs from 'fs';


const importStatement = /import\s+(?:(\*\s+as\s)?([\w-_]+),?)?\s*(?:\{([^\}]+)\})?\s+from\s+["']([^"']+)["']/gi;

export function rewriteImportToRequire(code: string): string {
return code.replace(importStatement, (str, wildcard: string, module: string, modules: string, from: string) => {
let rewrite = '';

if (module)
rewrite = `default: ${module}`;

if (modules)
rewrite += (rewrite && ', ') + modules.replace(/\sas\s/g, ': ');

return `const ${wildcard ? module : `{ ${rewrite} }`} = require('${from}');`;
});
}


const requireStatement = /require\s*\(\s*(['"])([A-Z0-9_~\\\/\.]+)\s*\1\)/gi;

export function rewriteModulePathInRequire(code: string, basePath: string, filePath: string): string {
return code.replace(requireStatement, (str, par, name) => {
let path = Path.join(basePath, 'node_modules', name);

if (Fs.existsSync(path) === false)
path = (name.indexOf('/') === -1)
? name
: (filePath)
? Path.join(Path.dirname(filePath), name)
: Path.join(basePath, name);

return `require('${path.replace(/\\/g, '\\\\')}')`;
});
}


const lineBreak = /\r?\n/;
const consoleLogCall = /console\s*\.(debug|error|info|log|warn)\(/g;

export function rewriteConsoleToAppendLineNumber(code: string): string {
let num = 0,
out = [];

for (let line of code.split(lineBreak)) {
out.push(line.replace(consoleLogCall, (str, method) => `global['\`console\`'].${method}(${num}, `));
num++;
}

return out.join('\n');
}


const lineBreakInChainCall = /([\n\s]+)\./gi;

export function rewriteChainCallInOneLine(code: string): string {
return code.replace(lineBreakInChainCall, (str, whitespace) => `/*\`${whitespace.split(lineBreak).length - 1}\`*/.`);
}
110 changes: 110 additions & 0 deletions src/decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
DecorationOptions,
DecorationRangeBehavior,
MarkdownString,
OutputChannel,
Position,
Range,
TextEditor,
window,
} from "vscode";


// create a decorator type that we use to decorate small numbers
const resultDecorationType = window.createTextEditorDecorationType({
rangeBehavior: DecorationRangeBehavior.ClosedClosed,
light: {},
dark: {},
});
const colorOfType = {
'Value of Expression': 'green',
'Console': '#457abb',
'Error': 'red',
}
type Data = { line: number, value: string };
type Result = { line: number, type: 'Value of Expression' | 'Console' | 'Error', text: string, value: string };

export default class Decorator {
private editor: TextEditor;
private lineToOutput: Map<number, any> = new Map();
private lineToDecorator: Map<number, DecorationOptions> = new Map();
private decorators: DecorationOptions[] = [];

constructor(private outputChannel: OutputChannel) { }

init(editor: TextEditor) {
this.editor = editor;
this.lineToOutput.clear();
this.lineToDecorator.clear();
this.decorators = [];
}

async update(data: Data) {
if (!data) return;

let result = this.format(data);

this.outputChannel.appendLine(` ${result.value}`);

if (result.type === 'Console') {
result = this.mergeConsoleOutput(result);
}
this.updateWith(result);
this.decorateAll();
}
private format({ line, value }: Data): Result {
let match: RegExpExecArray;

if ((match = /((\w*Error(?:\s\[[^\]]+\])?:\s.*)(?:\n\s*at\s[\s\S]+)?)$/.exec(value)) != null) {
return { line, type: 'Error', text: match[2], value: match[1] };
}
else if ((match = /^`\{(\d+)\}`([\s\S]*)$/.exec(value)) != null) {
let value = match[2] || '';
return { line: +match[1], type: 'Console', text: value.replace(/\r?\n/g, ' '), value };
}
else {
return { line, type: 'Value of Expression', text: value.replace(/\r?\n/g, ' '), value };
}
}
private mergeConsoleOutput({ line, text, value }: Result) {
let output = this.lineToOutput.get(line);
if (output == null) {
this.lineToOutput.set(line, output = { line, type: 'Console', text: '', value: '' });
}

output.text += (output.text && ', ') + text;
output.value += (output.value && '\n') + value;

return output;
}
private updateWith({ line, type, text, value }: Result) {
let decorator: DecorationOptions;

if ((decorator = this.lineToDecorator.get(line)) == null) {
let pos = new Position(line, Number.MAX_SAFE_INTEGER);

decorator = {
renderOptions: { before: { margin: '0 0 0 1em' } },
range: new Range(pos, pos)
};
this.lineToDecorator.set(line, decorator);
this.decorators.push(decorator);
}

decorator.renderOptions.before.color = colorOfType[type];
decorator.renderOptions.before.contentText = ` ${text}`;

decorator.hoverMessage = new MarkdownString(type)
.appendCodeblock(value, type === 'Error' ? 'text' : 'javascript');
}

decorateAll() {
this.decorate(this.decorators);
}
decorateExcept(line: number) {
this.decorate(this.decorators.filter(d => line != d.range.start.line));
}
private decorate(decorations: DecorationOptions[]) {
this.editor.setDecorations(resultDecorationType, decorations);
}
}
Loading