Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
5945c04cce | |||
56e27a2d14 | |||
3f8f515262 | |||
3656d71e6d | |||
1ce0b2e4e2 | |||
9ab72c9ccb | |||
6f5357ea5c | |||
b2014942b6 | |||
e549845d70 | |||
7e5b68116a | |||
10850c9d98 | |||
e8ce9b73ae | |||
fc7e3b431f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
client/.antlr
|
||||
test.bf
|
||||
out
|
||||
dist
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"editor.insertSpaces": false,
|
||||
"tslint.enable": true,
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
24
README.md
24
README.md
@@ -13,14 +13,30 @@ A simple language server based VSCode Extension for the (Branflakes?) (BrainFuck
|
||||
|
||||
### Execution
|
||||
|
||||
Use the command to execute the code.
|
||||
Issue is, because BF is a **turing complete** language, there is no way to know if the program will terminate or not. Hence for now, the command may lead to infinite execution.
|
||||
If the program requires input, it will be requested as a prompt.
|
||||
Use the BF execute task to execute the code.
|
||||
Either run the "current file" task, or create a customized task with the required file.
|
||||
I/O is done on the created terminal window.
|
||||
|
||||
TODO: Implement a timeout.
|
||||
|
||||
There is also an older command to run the code, where output is shown as a status message. Here, if the program requires input, it will be requested as a prompt.
|
||||
|
||||
### Changelog
|
||||
|
||||
#### 0.3.0
|
||||
|
||||

|
||||
|
||||
- Added a task for execution
|
||||
- Press Control C to halt it while its waiting for input
|
||||
- Close task to abort execution
|
||||
- Detail: The program will halt between loop iterations.
|
||||
- Migrated the run command to `bf.execute.old`
|
||||
|
||||
#### 0.2.1
|
||||
|
||||
- Change category
|
||||
- Small bugfix for brackets validation
|
||||
|
||||
#### 0.2.0
|
||||
|
||||
- Cycle input pointer on overflow/underflow
|
||||
|
BIN
assets/command.gif
Normal file
BIN
assets/command.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 540 KiB |
20
client/src/BranFlakesExecutorGen.ts
Normal file
20
client/src/BranFlakesExecutorGen.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { EventEmitter } from 'vscode';
|
||||
import InputStrategy from './input/InputStrategy';
|
||||
import {BranFlakesExecutorVisitor} from './exec/BranFlakesExecutorVisitor';
|
||||
|
||||
export class BranFlakesStreamingExecutor {
|
||||
|
||||
constructor(private fileData: string, private fileName: string = 'fileName:dummy', private emitter: EventEmitter<string>,
|
||||
private inputStrategy: InputStrategy
|
||||
) { }
|
||||
|
||||
|
||||
async run() {
|
||||
const finalOutput = await BranFlakesExecutorVisitor.run(
|
||||
this.fileData,
|
||||
this.fileName,
|
||||
this.inputStrategy,
|
||||
async (str) => { this.emitter.fire(str); }
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,140 +0,0 @@
|
||||
import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor';
|
||||
import { LoopStmtContext } from './generated/bfParser';
|
||||
import { bfVisitor } from './generated/bfVisitor';
|
||||
import { DiagnosticSeverity } from 'vscode-languageclient';
|
||||
import { getTree } from './BranFlakesParseRunner';
|
||||
import { RuleNode } from 'antlr4ts/tree/RuleNode';
|
||||
import InputStrategy from './input/InputStrategy';
|
||||
|
||||
export default class BranFlakesExecutorVisitor
|
||||
extends AbstractParseTreeVisitor<Promise<void>>
|
||||
implements bfVisitor<Promise<void>>
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @param input Input string
|
||||
* @param inputPtr Input pointer to start from
|
||||
*/
|
||||
constructor(
|
||||
private inputStrategy: InputStrategy,
|
||||
private logger: (val: string) => Thenable<string>,
|
||||
private inputPtr: number = 0
|
||||
) {
|
||||
super();
|
||||
}
|
||||
// /**
|
||||
// * The memory cells (Can work with negative cells this way)
|
||||
// */
|
||||
// private cells: Map<number, number> = new Map();
|
||||
|
||||
private byteArraySize: number = 30000;
|
||||
private byteArray: Int8Array = new Int8Array(this.byteArraySize);
|
||||
/**
|
||||
* Pointer
|
||||
*/
|
||||
private ptr: number = 0;
|
||||
/** Output string */
|
||||
private outputStr: string = '';
|
||||
|
||||
|
||||
|
||||
defaultResult() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
/**
|
||||
* Run a file
|
||||
* @param text
|
||||
* @param fn
|
||||
* @param inputStrategy
|
||||
* @returns
|
||||
*/
|
||||
static async run(
|
||||
text: string,
|
||||
fn: string,
|
||||
inputStrategy: InputStrategy,
|
||||
logger: (str: string) => Thenable<string>
|
||||
) {
|
||||
//get tree and issues
|
||||
const { tree, issues } = getTree(text, fn);
|
||||
|
||||
//get only errors
|
||||
const x = issues.filter(e => e.type === DiagnosticSeverity.Error);
|
||||
//if any error, drop
|
||||
if (x.length > 0) {
|
||||
throw Error('Errors exist');
|
||||
}
|
||||
// make visitor
|
||||
const vis = new BranFlakesExecutorVisitor(inputStrategy, logger);
|
||||
//visit the tree
|
||||
await vis.visit(tree);
|
||||
|
||||
//get output
|
||||
return vis.outputStr;
|
||||
}
|
||||
|
||||
getCell(pointerIndex: number) {
|
||||
return this.byteArray[pointerIndex];
|
||||
}
|
||||
setCell(pointerIndex: number, value: number): void {
|
||||
this.byteArray[pointerIndex] = value;
|
||||
}
|
||||
|
||||
async visitLoopStmt(ctx: LoopStmtContext) {
|
||||
while ((this.getCell(this.ptr) ?? 0) !== 0) {
|
||||
await this.visitChildren(ctx);
|
||||
}
|
||||
}
|
||||
async visitPtrLeft() {
|
||||
this.ptr = (this.ptr + this.byteArraySize - 1) % this.byteArraySize;
|
||||
}
|
||||
async visitPtrRight() {
|
||||
this.ptr = (this.ptr + this.byteArraySize + 1) % this.byteArraySize;
|
||||
}
|
||||
async visitPtrIncr() {
|
||||
const val = this.getCell(this.ptr);
|
||||
this.setCell(this.ptr, (val + 1) % 256);
|
||||
}
|
||||
async visitPtrDecr() {
|
||||
const val = this.getCell(this.ptr);
|
||||
this.setCell(this.ptr, (val + 255) % 256);
|
||||
}
|
||||
async visitOutputStmt() {
|
||||
const val = this.getCell(this.ptr) ?? 0;
|
||||
const str = String.fromCharCode(val);
|
||||
|
||||
this.outputStr += str;
|
||||
}
|
||||
|
||||
async visitInputStmt() {
|
||||
//get char
|
||||
const char = (await this.inputStrategy.getInput()) ?? 0;
|
||||
//increment the input pointer after this
|
||||
this.inputPtr++;
|
||||
this.setCell(this.ptr, char);
|
||||
}
|
||||
|
||||
// override for maintaining async
|
||||
async visitChildren(node: RuleNode): Promise<void> {
|
||||
let result = this.defaultResult();
|
||||
await result;
|
||||
let n = node.childCount;
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (!this.shouldVisitNextChild(node, result)) {
|
||||
break;
|
||||
}
|
||||
let c = node.getChild(i);
|
||||
let childResult = c.accept(this);
|
||||
result = this.aggregateResult(result, childResult);
|
||||
await result;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
// override for maintaining async
|
||||
protected async aggregateResult(
|
||||
aggregate: Promise<void>,
|
||||
nextResult: Promise<void>
|
||||
): Promise<void> {
|
||||
await aggregate;
|
||||
return nextResult;
|
||||
}
|
||||
}
|
4
client/src/command/BranFlakesCommand.ts
Normal file
4
client/src/command/BranFlakesCommand.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface BranFlakesCommand {
|
||||
getCommandName(): string;
|
||||
getCommandHandler(): (...args: any) => Promise<any>;
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
|
||||
export interface Command{
|
||||
getCommandName():string;
|
||||
getCommandHandler():(...args:any)=>Promise<any>;
|
||||
}
|
29
client/src/command/CompileBranFlakesCommand.ts
Normal file
29
client/src/command/CompileBranFlakesCommand.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { window } from 'vscode';
|
||||
import type { BranFlakesCommand } from './BranFlakesCommand';
|
||||
import { VSCodePromptInputStrategy } from '../input/VSCodePromptInputStrategy';
|
||||
|
||||
export class CompileBranFlakesCommand implements BranFlakesCommand {
|
||||
getCommandName() {
|
||||
return 'bf.execute.old';
|
||||
}
|
||||
getCommandHandler() {
|
||||
return async () => {
|
||||
const text = window.activeTextEditor.document.getText();
|
||||
const fn = window.activeTextEditor.document.fileName;
|
||||
const inputStrategy = new VSCodePromptInputStrategy(
|
||||
window.showInputBox
|
||||
);
|
||||
const { BranFlakesExecutorVisitor } = await import(
|
||||
'../exec/BranFlakesExecutorVisitor'
|
||||
);
|
||||
|
||||
const output = await BranFlakesExecutorVisitor.run(
|
||||
text,
|
||||
fn,
|
||||
inputStrategy,
|
||||
window.showInformationMessage
|
||||
);
|
||||
await window.showInformationMessage(`Output: ${output}`);
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
import { window } from 'vscode';
|
||||
import { Command as BranFlakesCommand } from './Command';
|
||||
import { VSCodePromptInputStrategy } from '../input/VSCodePromptInputStrategy';
|
||||
import BranFlakesExecutorVisitor from '../BranFlakesExecutorVisitor';
|
||||
|
||||
export class CompileBranFlakesCommand implements BranFlakesCommand {
|
||||
getCommandName() {
|
||||
return 'bf.execute';
|
||||
}
|
||||
getCommandHandler() {
|
||||
return async () => {
|
||||
const text = window.activeTextEditor.document.getText();
|
||||
const fn = window.activeTextEditor.document.fileName;
|
||||
const inputStrategy = new VSCodePromptInputStrategy(
|
||||
window.showInputBox
|
||||
);
|
||||
const output = await BranFlakesExecutorVisitor.run(
|
||||
text,
|
||||
fn,
|
||||
inputStrategy,
|
||||
window.showInformationMessage
|
||||
);
|
||||
await window.showInformationMessage(`Output: ${output}`);
|
||||
};
|
||||
}
|
||||
}
|
11
client/src/exec/AbortRequestor.ts
Normal file
11
client/src/exec/AbortRequestor.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
export class AbortClassRequestor{
|
||||
private abortRequest=false;
|
||||
|
||||
requestAbort(){
|
||||
this.abortRequest=true;
|
||||
}
|
||||
isAborted(){
|
||||
return this.abortRequest;
|
||||
}
|
||||
}
|
147
client/src/exec/BranFlakesExecutorVisitor.ts
Normal file
147
client/src/exec/BranFlakesExecutorVisitor.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor';
|
||||
import { LoopStmtContext } from '../generated/bfParser';
|
||||
import { bfVisitor } from '../generated/bfVisitor';
|
||||
import { DiagnosticSeverity } from 'vscode-languageclient';
|
||||
import { getTree } from '../BranFlakesParseRunner';
|
||||
import { RuleNode } from 'antlr4ts/tree/RuleNode';
|
||||
import type InputStrategy from '../input/InputStrategy';
|
||||
import { AbortClassRequestor } from './AbortRequestor';
|
||||
|
||||
export class BranFlakesExecutorVisitor
|
||||
extends AbstractParseTreeVisitor<Promise<void>>
|
||||
implements bfVisitor<Promise<void>> {
|
||||
/**
|
||||
*
|
||||
* @param input Input string
|
||||
* @param inputPtr Input pointer to start from
|
||||
*/
|
||||
constructor(
|
||||
private inputStrategy: InputStrategy,
|
||||
private logger: (val: string) => Thenable<void>,
|
||||
private abortRequestor?: AbortClassRequestor,
|
||||
private inputPtr: number = 0
|
||||
) {
|
||||
super();
|
||||
}
|
||||
// /**
|
||||
// * The memory cells (Can work with negative cells this way)
|
||||
// */
|
||||
// private cells: Map<number, number> = new Map();
|
||||
|
||||
private byteArraySize: number = 30000;
|
||||
private byteArray: Int8Array = new Int8Array(this.byteArraySize);
|
||||
/**
|
||||
* Pointer
|
||||
*/
|
||||
private ptr: number = 0;
|
||||
/** Output string */
|
||||
private outputStr: string = '';
|
||||
|
||||
|
||||
defaultResult() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
/**
|
||||
* Run a file
|
||||
* @param text
|
||||
* @param fn
|
||||
* @param inputStrategy
|
||||
* @returns
|
||||
*/
|
||||
static async run(
|
||||
text: string,
|
||||
fn: string,
|
||||
inputStrategy: InputStrategy,
|
||||
logger: (str: string) => Thenable<void>,
|
||||
aborter?: AbortClassRequestor
|
||||
) {
|
||||
//get tree and issues
|
||||
const { tree, issues } = getTree(text, fn);
|
||||
|
||||
//get only errors
|
||||
const x = issues.filter(e => e.type === DiagnosticSeverity.Error);
|
||||
//if any error, drop
|
||||
if (x.length > 0) {
|
||||
throw Error('Errors exist');
|
||||
}
|
||||
// make visitor
|
||||
const vis = new BranFlakesExecutorVisitor(inputStrategy, logger, aborter);
|
||||
//visit the tree
|
||||
await vis.visit(tree);
|
||||
|
||||
//get output
|
||||
return vis.outputStr;
|
||||
}
|
||||
|
||||
getCell(pointerIndex: number) {
|
||||
return this.byteArray[pointerIndex];
|
||||
}
|
||||
setCell(pointerIndex: number, value: number): void {
|
||||
this.byteArray[pointerIndex] = value;
|
||||
}
|
||||
|
||||
async visitLoopStmt(ctx: LoopStmtContext) {
|
||||
while ((this.getCell(this.ptr) ?? 0) !== 0) {
|
||||
await this.visitChildren(ctx);
|
||||
}
|
||||
}
|
||||
async visitPtrLeft() {
|
||||
this.ptr = (this.ptr + this.byteArraySize - 1) % this.byteArraySize;
|
||||
}
|
||||
async visitPtrRight() {
|
||||
this.ptr = (this.ptr + this.byteArraySize + 1) % this.byteArraySize;
|
||||
}
|
||||
async visitPtrIncr() {
|
||||
const val = this.getCell(this.ptr);
|
||||
this.setCell(this.ptr, (val + 1) % 256);
|
||||
}
|
||||
async visitPtrDecr() {
|
||||
const val = this.getCell(this.ptr);
|
||||
this.setCell(this.ptr, (val + 255) % 256);
|
||||
}
|
||||
async visitOutputStmt() {
|
||||
const val = this.getCell(this.ptr) ?? 0;
|
||||
const str = String.fromCharCode(val);
|
||||
|
||||
this.outputStr += str;
|
||||
await this.logger(str);
|
||||
}
|
||||
|
||||
async visitInputStmt() {
|
||||
//get char
|
||||
const char = (await this.inputStrategy.getInput()) ?? 0;
|
||||
if (char === 3) { throw Error('Halt input wait'); }
|
||||
//increment the input pointer after this
|
||||
this.inputPtr++;
|
||||
this.setCell(this.ptr, char);
|
||||
}
|
||||
|
||||
// override for maintaining async
|
||||
async visitChildren(node: RuleNode): Promise<void> {
|
||||
let result = this.defaultResult();
|
||||
await result;
|
||||
let n = node.childCount;
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (!this.shouldVisitNextChild(node, result)) {
|
||||
break;
|
||||
}
|
||||
let c = node.getChild(i);
|
||||
let childResult = c.accept(this);
|
||||
result = this.aggregateResult(result, childResult);
|
||||
await result;
|
||||
}
|
||||
// break for any close requests
|
||||
return new Promise((res, rej) => {
|
||||
|
||||
setTimeout(() => { if (this.abortRequestor?.isAborted()??false) {rej('aborted');} else {res(undefined);} }, 0);
|
||||
});
|
||||
}
|
||||
// override for maintaining async
|
||||
protected async aggregateResult(
|
||||
aggregate: Promise<void>,
|
||||
nextResult: Promise<void>
|
||||
): Promise<void> {
|
||||
await aggregate;
|
||||
return nextResult;
|
||||
}
|
||||
}
|
@@ -4,68 +4,81 @@
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import * as path from 'path';
|
||||
import { ExtensionContext, commands, window } from 'vscode';
|
||||
import { commands, tasks, workspace,window } from 'vscode';
|
||||
import type { Disposable, ExtensionContext } from 'vscode';
|
||||
import {
|
||||
LanguageClient,
|
||||
LanguageClientOptions,
|
||||
ServerOptions,
|
||||
TransportKind,
|
||||
LanguageClient,
|
||||
LanguageClientOptions,
|
||||
ServerOptions,
|
||||
TransportKind,
|
||||
} from 'vscode-languageclient';
|
||||
import { CompileBranFlakesCommand } from './command/CompileCommand';
|
||||
import { CompileBranFlakesCommand } from './command/CompileBranFlakesCommand';
|
||||
import { CustomExecutionTaskProvider } from './task/CustomExecutionTerminal';
|
||||
|
||||
let client: LanguageClient;
|
||||
|
||||
let bfRunTaskProvider: Disposable;
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
// The server is implemented in node
|
||||
let serverModule = context.asAbsolutePath(
|
||||
path.join('server', 'dist', 'server.js')
|
||||
);
|
||||
// The debug options for the server
|
||||
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
|
||||
let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };
|
||||
// The server is implemented in node
|
||||
let serverModule = context.asAbsolutePath(
|
||||
path.join('server', 'dist', 'server.js')
|
||||
);
|
||||
// The debug options for the server
|
||||
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
|
||||
let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };
|
||||
|
||||
// If the extension is launched in debug mode then the debug server options are used
|
||||
// Otherwise the run options are used
|
||||
let serverOptions: ServerOptions = {
|
||||
run: { module: serverModule, transport: TransportKind.ipc },
|
||||
debug: {
|
||||
module: serverModule,
|
||||
transport: TransportKind.ipc,
|
||||
options: debugOptions,
|
||||
},
|
||||
};
|
||||
// If the extension is launched in debug mode then the debug server options are used
|
||||
// Otherwise the run options are used
|
||||
let serverOptions: ServerOptions = {
|
||||
run: { module: serverModule, transport: TransportKind.ipc },
|
||||
debug: {
|
||||
module: serverModule,
|
||||
transport: TransportKind.ipc,
|
||||
options: debugOptions,
|
||||
},
|
||||
};
|
||||
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
// Register the server for plain text documents
|
||||
documentSelector: [{ scheme: 'file', language: 'bf' }],
|
||||
};
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
// Register the server for plain text documents
|
||||
documentSelector: [{ scheme: 'file', language: 'bf' }],
|
||||
};
|
||||
|
||||
const branFlakesCommands = [new CompileBranFlakesCommand()];
|
||||
for (let branFlakesCommand of branFlakesCommands) {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
branFlakesCommand.getCommandName(),
|
||||
branFlakesCommand.getCommandHandler()
|
||||
)
|
||||
);
|
||||
}
|
||||
const branFlakesCommands = [new CompileBranFlakesCommand()];
|
||||
for (let branFlakesCommand of branFlakesCommands) {
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
branFlakesCommand.getCommandName(),
|
||||
branFlakesCommand.getCommandHandler()
|
||||
),
|
||||
|
||||
// Create the language client and start the client.
|
||||
client = new LanguageClient(
|
||||
'brainfucklanguageserver',
|
||||
'Brainfuck Language Server',
|
||||
serverOptions,
|
||||
clientOptions
|
||||
);
|
||||
|
||||
// Start the client. This will also launch the server
|
||||
client.start();
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Create the language client and start the client.
|
||||
client = new LanguageClient(
|
||||
'brainfucklanguageserver',
|
||||
'Brainfuck Language Server',
|
||||
serverOptions,
|
||||
clientOptions
|
||||
);
|
||||
|
||||
// Start the client. This will also launch the server
|
||||
client.start();
|
||||
|
||||
const workspaceRoot = (workspace.workspaceFolders && (workspace.workspaceFolders.length > 0))
|
||||
? workspace.workspaceFolders[0].uri.fsPath : undefined;
|
||||
|
||||
bfRunTaskProvider = tasks.registerTaskProvider(CustomExecutionTaskProvider.type, new CustomExecutionTaskProvider(workspaceRoot));
|
||||
}
|
||||
|
||||
export function deactivate(): Thenable<void> | undefined {
|
||||
if (!client) {
|
||||
return undefined;
|
||||
}
|
||||
return client.stop();
|
||||
if (!client) {
|
||||
return undefined;
|
||||
}
|
||||
bfRunTaskProvider?.dispose();
|
||||
client?.stop();
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
|
||||
export default interface InputStrategy {
|
||||
getInput(): Promise<number>;
|
||||
}
|
||||
};
|
@@ -1,5 +1,5 @@
|
||||
import { CancellationToken, InputBoxOptions } from 'vscode';
|
||||
import InputStrategy from './InputStrategy';
|
||||
import type { CancellationToken, InputBoxOptions } from 'vscode';
|
||||
import type InputStrategy from './InputStrategy';
|
||||
|
||||
export class VSCodePromptInputStrategy implements InputStrategy {
|
||||
private inputQueue: string = '';
|
||||
@@ -8,21 +8,26 @@ export class VSCodePromptInputStrategy implements InputStrategy {
|
||||
promptOptions?: InputBoxOptions,
|
||||
cancelToken?: CancellationToken
|
||||
) => Thenable<string>
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async getInput(): Promise<number> {
|
||||
while (this.inputQueue.length == 0) {
|
||||
await this.requestInputFromPrompt();
|
||||
}
|
||||
const character = this.inputQueue.charCodeAt(0);
|
||||
this.inputQueue = this.inputQueue.substring(1);
|
||||
while (this.inputQueue.length === 0) {
|
||||
await this.requestInputFromPrompt();
|
||||
}
|
||||
const character = this.popInputFromQueue();
|
||||
|
||||
return character;
|
||||
return character;
|
||||
}
|
||||
private popInputFromQueue() {
|
||||
const character = this.inputQueue.charCodeAt(0);
|
||||
this.inputQueue = this.inputQueue.substring(1);
|
||||
return character;
|
||||
}
|
||||
|
||||
private async requestInputFromPrompt() {
|
||||
const inputPrompt = await this.requestor({
|
||||
prompt: 'More input is required. Please provide input:',
|
||||
});
|
||||
this.inputQueue += inputPrompt;
|
||||
const inputPrompt = await this.requestor({
|
||||
prompt: 'More input is required. Please provide input:',
|
||||
});
|
||||
this.inputQueue += inputPrompt;
|
||||
}
|
||||
}
|
||||
|
152
client/src/task/CustomExecutionTerminal.ts
Normal file
152
client/src/task/CustomExecutionTerminal.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { BranFlakesExecutorVisitor } from '../exec/BranFlakesExecutorVisitor';
|
||||
import { AbortClassRequestor } from '../exec/AbortRequestor';
|
||||
|
||||
interface BFRunTaskDefinition extends vscode.TaskDefinition {
|
||||
file?: string;
|
||||
}
|
||||
|
||||
export class CustomExecutionTaskProvider implements vscode.TaskProvider {
|
||||
static type: string = 'bf-run';
|
||||
tasks: vscode.Task[] | undefined;
|
||||
|
||||
constructor(private workspaceRoot: string | undefined) {
|
||||
|
||||
}
|
||||
|
||||
provideTasks(token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task[]> {
|
||||
if (this.tasks !== undefined) { return this.tasks; }
|
||||
|
||||
this.tasks = [this.getTaskFromDefinition(undefined)];
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
|
||||
getTaskFromDefinition(fileName: string | undefined): vscode.Task {
|
||||
const definition: BFRunTaskDefinition = {
|
||||
type: CustomExecutionTaskProvider.type,
|
||||
file: fileName
|
||||
};
|
||||
return new vscode.Task(definition, vscode.TaskScope.Workspace, `bf: run: current file`, CustomExecutionTaskProvider.type,
|
||||
new vscode.CustomExecution(async () => {
|
||||
return new CustomBuildTaskTerminal(definition.file);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
resolveTask(_task: vscode.Task, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task> {
|
||||
const definition: BFRunTaskDefinition = <any>_task.definition;
|
||||
|
||||
const fileNameRecovered = definition.file;
|
||||
const taskName = `bf: run: ` + (fileNameRecovered ?? 'current file');
|
||||
return new vscode.Task(definition, vscode.TaskScope.Workspace, taskName, CustomExecutionTaskProvider.type,
|
||||
new vscode.CustomExecution(async () => {
|
||||
return new CustomBuildTaskTerminal(definition.file);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function replaceLFWithCRLF(data: string) {
|
||||
return data.replace(/(?<!\r)\n/gm, '\r\n');
|
||||
}
|
||||
|
||||
|
||||
class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
|
||||
private writeEmitter = new vscode.EventEmitter<string>();
|
||||
private closeEmitter = new vscode.EventEmitter<number>();
|
||||
|
||||
private readEmitter = new vscode.EventEmitter<void>();
|
||||
inputQueue: number[] = [];
|
||||
|
||||
private openDocument: vscode.TextDocument | undefined;
|
||||
onDidWrite: vscode.Event<string> = this.writeEmitter.event;
|
||||
onDidClose?: vscode.Event<number> = this.closeEmitter.event;
|
||||
abortRequestor = new AbortClassRequestor();
|
||||
|
||||
handleInput(data: string): void {
|
||||
// this.writeEmitter.fire(`Echo(${data.length})` + data);
|
||||
const newData = [...data].map(e => e.charCodeAt(0));
|
||||
console.log('new input', newData);
|
||||
this.inputQueue.push(...newData);
|
||||
this.writeEmitter.fire(replaceLFWithCRLF(data));
|
||||
this.readEmitter.fire();
|
||||
}
|
||||
|
||||
|
||||
constructor(private fileName?: string) {
|
||||
}
|
||||
|
||||
open(_initialDimensions: vscode.TerminalDimensions | undefined): void {
|
||||
// At this point we can start using the terminal.
|
||||
this.openDocumentForTask().then(this.doExecution.bind(this));
|
||||
}
|
||||
|
||||
getPath(fileLocationString: string | undefined, fileName: string) {
|
||||
if (fileLocationString === undefined) { return vscode.Uri.file(fileName); }
|
||||
return vscode.Uri.file(path.resolve(fileLocationString, fileName));
|
||||
}
|
||||
|
||||
private async openDocumentForTask() {
|
||||
let openDocument: vscode.TextDocument;
|
||||
if (this.fileName !== undefined) {
|
||||
try {
|
||||
const fileLocationPathString = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath;
|
||||
const finalPath = this.getPath(fileLocationPathString, this.fileName);
|
||||
openDocument = await vscode.workspace.openTextDocument(finalPath);
|
||||
} catch (e) {
|
||||
vscode.window.showErrorMessage('Failed to open file ' + this.fileName);
|
||||
this.closeEmitter.fire(2);
|
||||
}
|
||||
} else {
|
||||
openDocument = vscode.window.activeTextEditor.document;
|
||||
}
|
||||
this.openDocument = openDocument;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
// The terminal has been closed. Shutdown the build.
|
||||
console.log('Forced close');
|
||||
this.abortRequestor.requestAbort();
|
||||
}
|
||||
|
||||
private async doExecution(): Promise<void> {
|
||||
this.writeEmitter.fire('[bf] Requested execution of ' + (this.fileName ?? 'active file') + '\r\n');
|
||||
const cus = this;
|
||||
try {
|
||||
|
||||
await BranFlakesExecutorVisitor.run(this.openDocument.getText(),
|
||||
this.openDocument.uri.fsPath,
|
||||
{
|
||||
getInput() {
|
||||
return new Promise((res, rej) => {
|
||||
if (cus.inputQueue.length > 0) {
|
||||
const char = cus.inputQueue.shift();
|
||||
res(char);
|
||||
} else {
|
||||
const dispose: vscode.Disposable[] = [];
|
||||
cus.readEmitter.event(e => {
|
||||
const char = cus.inputQueue.shift();
|
||||
//clear the earliest disposable
|
||||
dispose.shift()?.dispose();
|
||||
res(char);
|
||||
}, null, dispose);
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
async (data) => {
|
||||
this.writeEmitter.fire(replaceLFWithCRLF(data));
|
||||
},this.abortRequestor
|
||||
);
|
||||
this.closeEmitter.fire(0);
|
||||
} catch (e) {
|
||||
this.closeEmitter.fire(1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -16,7 +16,8 @@ const config = {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
devtoolModuleFilenameTemplate: '../[resource-path]'
|
||||
sourceMapFilename: '[name].js.map',
|
||||
devtoolModuleFilenameTemplate: '../[resource-path]',
|
||||
},
|
||||
// devtool: 'source-map',
|
||||
externals: {
|
||||
@@ -27,6 +28,7 @@ const config = {
|
||||
innerGraph:true,
|
||||
usedExports:true
|
||||
},
|
||||
|
||||
resolve: {
|
||||
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
|
||||
extensions: ['.ts', '.js'],
|
||||
|
8157
package-lock.json
generated
8157
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -5,9 +5,9 @@
|
||||
"author": "Atreya Bain",
|
||||
"license": "MIT",
|
||||
"publisher": "atreyabain",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.1",
|
||||
"icon": "assets/128.png",
|
||||
"categories": [],
|
||||
"categories": ["Programming Languages","Linters"],
|
||||
"keywords": [
|
||||
"multi-root ready",
|
||||
"brainfuck",
|
||||
@@ -29,7 +29,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/chrisvrose/bf-server"
|
||||
},
|
||||
"main": "./client/dist/extension",
|
||||
"main": "./client/dist/extension.js",
|
||||
"contributes": {
|
||||
"languages": [
|
||||
{
|
||||
@@ -57,7 +57,7 @@
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"command": "bf.execute",
|
||||
"command": "bf.execute.old",
|
||||
"title": "BF: Execute",
|
||||
"when": "editorLangId == bf",
|
||||
"enablement": "editorLangId == bf"
|
||||
@@ -85,7 +85,18 @@
|
||||
"description": "Traces the communication between VS Code and the language server."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskDefinitions": [
|
||||
{
|
||||
"type": "bf-run",
|
||||
"properties": {
|
||||
"file":{
|
||||
"type":"string",
|
||||
"description": "The BF file to be executed. Can be omitted to run current file"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
|
128
server/src/connection.ts
Normal file
128
server/src/connection.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { CompletionItem, CompletionItemKind, Connection, DidChangeConfigurationNotification, DidChangeConfigurationParams, InitializeParams, InitializeResult, TextDocumentPositionParams, TextDocuments, TextDocumentSyncKind } from 'vscode-languageserver';
|
||||
import { validateTextDocument } from './validation';
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import { BranFlakesSettings, defaultSettings, SettingsManager } from './settings';
|
||||
|
||||
export class BranFlakesConnectionManager {
|
||||
|
||||
|
||||
constructor(protected connection: Connection, private validator:typeof validateTextDocument, private documents:TextDocuments<TextDocument>, private settingsManager:SettingsManager) {
|
||||
connection.onInitialize(this.initConnection.bind(this));
|
||||
connection.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this));
|
||||
}
|
||||
|
||||
|
||||
|
||||
initConnection(params: InitializeParams) {
|
||||
let capabilities = params.capabilities;
|
||||
|
||||
// Does the client support the `workspace/configuration` request?
|
||||
// If not, we will fall back using global settings
|
||||
this.settingsManager.hasConfigurationCapability = !!(
|
||||
capabilities.workspace && !!capabilities.workspace.configuration
|
||||
);
|
||||
this.settingsManager.hasWorkspaceFolderCapability = !!(
|
||||
capabilities.workspace && !!capabilities.workspace.workspaceFolders
|
||||
);
|
||||
this.settingsManager.hasDiagnosticRelatedInformationCapability = !!(
|
||||
capabilities.textDocument &&
|
||||
capabilities.textDocument.publishDiagnostics &&
|
||||
capabilities.textDocument.publishDiagnostics.relatedInformation
|
||||
);
|
||||
|
||||
const result: InitializeResult = {
|
||||
capabilities: {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
// Tell the client that the server supports code completion
|
||||
completionProvider: {
|
||||
resolveProvider: false,
|
||||
triggerCharacters: ['.'],
|
||||
},
|
||||
},
|
||||
};
|
||||
if (this.settingsManager.hasWorkspaceFolderCapability) {
|
||||
result.capabilities.workspace = {
|
||||
workspaceFolders: {
|
||||
supported: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
onDidChangeConfiguration(change:DidChangeConfigurationParams){
|
||||
if (this.settingsManager.hasConfigurationCapability) {
|
||||
// Reset all cached document settings
|
||||
this.settingsManager.clearDocumentSettings();
|
||||
} else {
|
||||
this.settingsManager.updateSettings(
|
||||
(change.settings.languageServerExample || defaultSettings)
|
||||
);
|
||||
}
|
||||
|
||||
// Revalidate all open text documents
|
||||
Promise.all(this.documents.all().map(validateTextDocument)).catch(e => {
|
||||
this.connection.console.log('Failed to validate text documents');
|
||||
});
|
||||
}
|
||||
onInit() {
|
||||
if (this.settingsManager.hasConfigurationCapability) {
|
||||
// Register for all configuration changes.
|
||||
this.connection.client.register(
|
||||
DidChangeConfigurationNotification.type,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
if (this.settingsManager.hasWorkspaceFolderCapability) {
|
||||
this.connection.workspace.onDidChangeWorkspaceFolders(_event => {
|
||||
// connection.console.log('Workspace folder change event received.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setOnCompletion() {
|
||||
this.connection.onCompletion((_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
|
||||
const completions: CompletionItem[] = [
|
||||
{
|
||||
label: '+',
|
||||
detail: 'Addition',
|
||||
documentation: 'Add one to cell',
|
||||
},
|
||||
{
|
||||
label: '-',
|
||||
detail: 'Subtraction',
|
||||
documentation: 'Subtract one from cell',
|
||||
},
|
||||
{
|
||||
label: ',',
|
||||
detail: 'Input',
|
||||
documentation: 'Ask for input (Stored in the ASCII format)',
|
||||
},
|
||||
{
|
||||
label: '.',
|
||||
detail: 'Output',
|
||||
documentation: 'Output the equivalent ASCII character',
|
||||
},
|
||||
{
|
||||
label: '>',
|
||||
detail: 'Right Shift',
|
||||
documentation: 'Shift the pointer one cell to the right',
|
||||
},
|
||||
{
|
||||
label: '<',
|
||||
detail: 'Left Shift',
|
||||
documentation: 'Shift the pointer one cell to the Left',
|
||||
},
|
||||
];
|
||||
return completions.map(e => {
|
||||
e.kind = CompletionItemKind.Operator;
|
||||
return e;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
listen() {
|
||||
this.connection.listen();
|
||||
}
|
||||
|
||||
}
|
@@ -5,119 +5,33 @@
|
||||
import {
|
||||
createConnection,
|
||||
TextDocuments,
|
||||
Diagnostic,
|
||||
DiagnosticSeverity,
|
||||
ProposedFeatures,
|
||||
InitializeParams,
|
||||
DidChangeConfigurationNotification,
|
||||
CompletionItem,
|
||||
CompletionItemKind,
|
||||
TextDocumentPositionParams,
|
||||
TextDocumentSyncKind,
|
||||
InitializeResult,
|
||||
Position,
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
|
||||
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
|
||||
import { BranFlakesSettings, defaultSettings, SettingsManager } from './settings';
|
||||
import { BranFlakesConnectionManager } from './connection';
|
||||
import { validateTextDocument } from './validation';
|
||||
|
||||
// Create a connection for the server. The connection uses Node's IPC as a transport.
|
||||
// Also include all preview / proposed LSP features.
|
||||
let connection = createConnection(ProposedFeatures.all);
|
||||
|
||||
export let connection = createConnection(ProposedFeatures.all);
|
||||
|
||||
// Create a simple text document manager. The text document manager
|
||||
// supports full document sync only
|
||||
let documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
|
||||
|
||||
let hasConfigurationCapability: boolean = false;
|
||||
let hasWorkspaceFolderCapability: boolean = false;
|
||||
let hasDiagnosticRelatedInformationCapability: boolean = false;
|
||||
|
||||
connection.onInitialize((params: InitializeParams) => {
|
||||
let capabilities = params.capabilities;
|
||||
|
||||
// Does the client support the `workspace/configuration` request?
|
||||
// If not, we will fall back using global settings
|
||||
hasConfigurationCapability = !!(
|
||||
capabilities.workspace && !!capabilities.workspace.configuration
|
||||
);
|
||||
hasWorkspaceFolderCapability = !!(
|
||||
capabilities.workspace && !!capabilities.workspace.workspaceFolders
|
||||
);
|
||||
hasDiagnosticRelatedInformationCapability = !!(
|
||||
capabilities.textDocument &&
|
||||
capabilities.textDocument.publishDiagnostics &&
|
||||
capabilities.textDocument.publishDiagnostics.relatedInformation
|
||||
);
|
||||
|
||||
const result: InitializeResult = {
|
||||
capabilities: {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
// Tell the client that the server supports code completion
|
||||
completionProvider: {
|
||||
resolveProvider: false,
|
||||
triggerCharacters: ['.'],
|
||||
},
|
||||
},
|
||||
};
|
||||
if (hasWorkspaceFolderCapability) {
|
||||
result.capabilities.workspace = {
|
||||
workspaceFolders: {
|
||||
supported: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
connection.onInitialized(() => {
|
||||
if (hasConfigurationCapability) {
|
||||
// Register for all configuration changes.
|
||||
connection.client.register(
|
||||
DidChangeConfigurationNotification.type,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
if (hasWorkspaceFolderCapability) {
|
||||
connection.workspace.onDidChangeWorkspaceFolders(_event => {
|
||||
connection.console.log('Workspace folder change event received.');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// The example settings
|
||||
interface ExampleSettings {
|
||||
maxNumberOfProblems: number;
|
||||
}
|
||||
|
||||
// The global settings, used when the `workspace/configuration` request is not supported by the client.
|
||||
// Please note that this is not the case when using this server with the client provided in this example
|
||||
// but could happen with other clients.
|
||||
const defaultSettings: ExampleSettings = { maxNumberOfProblems: 5 };
|
||||
let globalSettings: ExampleSettings = defaultSettings;
|
||||
let globalSettings: BranFlakesSettings = defaultSettings;
|
||||
|
||||
// Cache the settings of all open documents
|
||||
let documentSettings: Map<string, Thenable<ExampleSettings>> = new Map();
|
||||
let documentSettings: Map<string, Thenable<BranFlakesSettings>> = new Map();
|
||||
let settingsManager = new SettingsManager();
|
||||
let cm = new BranFlakesConnectionManager(connection, validateTextDocument,documents,settingsManager);
|
||||
|
||||
connection.onDidChangeConfiguration(change => {
|
||||
if (hasConfigurationCapability) {
|
||||
// Reset all cached document settings
|
||||
documentSettings.clear();
|
||||
} else {
|
||||
globalSettings = <ExampleSettings>(
|
||||
(change.settings.languageServerExample || defaultSettings)
|
||||
);
|
||||
}
|
||||
|
||||
// Revalidate all open text documents
|
||||
documents.all().forEach(validateTextDocument);
|
||||
});
|
||||
|
||||
function getDocumentSettings(resource: string): Thenable<ExampleSettings> {
|
||||
if (!hasConfigurationCapability) {
|
||||
export function getDocumentSettings(resource: string): Thenable<BranFlakesSettings> {
|
||||
if (!settingsManager.hasConfigurationCapability) {
|
||||
return Promise.resolve(globalSettings);
|
||||
}
|
||||
let result = documentSettings.get(resource);
|
||||
@@ -133,13 +47,12 @@ function getDocumentSettings(resource: string): Thenable<ExampleSettings> {
|
||||
|
||||
// Only keep settings for open documents
|
||||
documents.onDidClose(e => {
|
||||
documentSettings.delete(e.document.uri);
|
||||
settingsManager.closeDocument(e.document.uri);
|
||||
});
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent(change => {
|
||||
//change.contentChanges;
|
||||
validateTextDocument(change.document);
|
||||
});
|
||||
|
||||
@@ -148,112 +61,12 @@ documents.onDidSave(change => {
|
||||
validateTextDocument(change.document);
|
||||
});
|
||||
|
||||
const validateBrackets = (text: string) => {
|
||||
let count = 0, lp: number[] = [],issues:number[]=[];
|
||||
const textsplit = text.split('');
|
||||
textsplit.forEach((x, i) => {
|
||||
if (x === '[' || x === ']') {
|
||||
|
||||
if (x === '[') {lp.push(i);}
|
||||
if (x === ']') {if(lp.length===0) {issues.push(i);}lp.pop();}
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return [...lp,...issues];
|
||||
};
|
||||
|
||||
async function validateTextDocument(textDocument: TextDocument): Promise<void> {
|
||||
// In this simple example we get the settings for every validate run.
|
||||
let settings = await getDocumentSettings(textDocument.uri);
|
||||
// The validator creates diagnostics for all uppercase words length 2 and more
|
||||
const text = textDocument.getText();
|
||||
|
||||
let problems = 0;
|
||||
let diagnostics: Diagnostic[] = [];
|
||||
const issues = validateBrackets(text);
|
||||
|
||||
diagnostics.push(...issues.map<Diagnostic>(e => ({
|
||||
message: 'Brackets unmatched',
|
||||
range:{
|
||||
start: textDocument.positionAt(e),
|
||||
end: textDocument.positionAt(e+1),
|
||||
},
|
||||
severity:DiagnosticSeverity.Error,
|
||||
code:'[ and ]',
|
||||
})));
|
||||
|
||||
// diagnostics.push({
|
||||
// message: 'Brackets not matched',
|
||||
// range: {
|
||||
// start: { line: 0, character: 0 },
|
||||
// end: { line: 0, character: 0 },
|
||||
// },
|
||||
// });
|
||||
|
||||
// diagnostics.push(<Diagnostic>{
|
||||
// severity: DiagnosticSeverity.Information,
|
||||
// range: {
|
||||
// start: textDocument.positionAt(0),
|
||||
// end: textDocument.positionAt(1),
|
||||
// },
|
||||
// // message:`HI:(${text})(${result.hasErrors()},${result.hasWarnings()})`,
|
||||
// message: `Parsing Failed`,
|
||||
// source: 'test',
|
||||
// });
|
||||
|
||||
// Send the computed diagnostics to VSCode.
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}
|
||||
|
||||
// This handler provides the initial list of the completion items.
|
||||
connection.onCompletion(
|
||||
(_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
|
||||
const completions: CompletionItem[] = [
|
||||
{
|
||||
label: '+',
|
||||
detail: 'Addition',
|
||||
documentation: 'Add one to cell',
|
||||
},
|
||||
{
|
||||
label: '-',
|
||||
detail: 'Subtraction',
|
||||
documentation: 'Subtract one from cell',
|
||||
},
|
||||
{
|
||||
label: ',',
|
||||
detail: 'Input',
|
||||
documentation: 'Ask for input (Stored in the ASCII format)',
|
||||
},
|
||||
{
|
||||
label: '.',
|
||||
detail: 'Output',
|
||||
documentation: 'Output the equivalent ASCII character',
|
||||
},
|
||||
{
|
||||
label: '>',
|
||||
detail: 'Right Shift',
|
||||
documentation: 'Shift the pointer one cell to the right',
|
||||
},
|
||||
{
|
||||
label: '<',
|
||||
detail: 'Left Shift',
|
||||
documentation: 'Shift the pointer one cell to the Left',
|
||||
},
|
||||
];
|
||||
return completions.map(e => {
|
||||
e.kind = CompletionItemKind.Operator;
|
||||
return e;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cm.setOnCompletion();
|
||||
|
||||
// Make the text document manager listen on the connection
|
||||
// for open, change and close text document events
|
||||
documents.listen(connection);
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
||||
cm.listen();
|
35
server/src/settings.ts
Normal file
35
server/src/settings.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// The example settings
|
||||
export interface BranFlakesSettings {
|
||||
maxNumberOfProblems: number;
|
||||
}
|
||||
|
||||
|
||||
// The global settings, used when the `workspace/configuration` request is not supported by the client.
|
||||
// Please note that this is not the case when using this server with the client provided in this example
|
||||
// but could happen with other clients.
|
||||
export const defaultSettings: BranFlakesSettings = {
|
||||
maxNumberOfProblems: 5
|
||||
};
|
||||
|
||||
|
||||
export class SettingsManager {
|
||||
hasConfigurationCapability: boolean = false;
|
||||
hasWorkspaceFolderCapability: boolean = false;
|
||||
hasDiagnosticRelatedInformationCapability: boolean = false;
|
||||
|
||||
documentSettings: Map<string, Thenable<BranFlakesSettings>> = new Map();
|
||||
|
||||
#settings = defaultSettings;
|
||||
|
||||
updateSettings(newSettings: BranFlakesSettings) {
|
||||
this.#settings = newSettings;
|
||||
}
|
||||
|
||||
closeDocument(doc: string) {
|
||||
this.documentSettings.delete(doc);
|
||||
|
||||
}
|
||||
clearDocumentSettings(){
|
||||
this.documentSettings.clear();
|
||||
}
|
||||
}
|
54
server/src/validation.ts
Normal file
54
server/src/validation.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import { getDocumentSettings, connection } from './server';
|
||||
|
||||
|
||||
|
||||
export const validateBrackets = (text: string) => {
|
||||
let count = 0, lp: number[] = [], issues: number[] = [];
|
||||
const textsplit = text.split('');
|
||||
textsplit.forEach((x, i) => {
|
||||
if (x === '[' || x === ']') {
|
||||
if (x === '[') {
|
||||
lp.push(i);
|
||||
}
|
||||
if (x === ']') {
|
||||
if (lp.length === 0) {
|
||||
issues.push(i);
|
||||
}
|
||||
lp.pop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [...lp, ...issues];
|
||||
};
|
||||
|
||||
|
||||
|
||||
export async function validateTextDocument(textDocument: TextDocument): Promise<void> {
|
||||
// In this simple example we get the settings for every validate run.
|
||||
let settings = await getDocumentSettings(textDocument.uri);
|
||||
// The validator creates diagnostics for all uppercase words length 2 and more
|
||||
const text = textDocument.getText();
|
||||
|
||||
let problems = 0;
|
||||
let diagnostics: Diagnostic[] = [];
|
||||
const issues = validateBrackets(text);
|
||||
|
||||
diagnostics.push(
|
||||
...issues.map<Diagnostic>(e => ({
|
||||
message: 'Brackets unmatched',
|
||||
range: {
|
||||
start: textDocument.positionAt(e),
|
||||
end: textDocument.positionAt(e + 1),
|
||||
},
|
||||
severity: DiagnosticSeverity.Error,
|
||||
code: '[ and ]',
|
||||
}))
|
||||
);
|
||||
|
||||
// Send the computed diagnostics to VSCode.
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}
|
||||
|
@@ -5,7 +5,8 @@
|
||||
"lib": ["ES2019"],
|
||||
"outDir": "out",
|
||||
"rootDir": "src",
|
||||
"sourceMap": true
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
Reference in New Issue
Block a user