https://segmentfault.com/a/1190000040712964 https://medium.com/better-programming/create-a-custom-web-editor-using-typescript-react-antlr-and-monaco-editor-bcfc7554e446

antlr4

https://github.com/amazzalel-habib/TodoLangEditor https://medium.com/better-programming/create-a-custom-web-editor-using-typescript-react-antlr-and-monaco-editor-bcfc7554e446 作者在最後有稍微提示要做自動補足要如何做,我們來補起來 直接run 專案可能會出錯 這邊我給修正過後的package.json

json
{
  "name": "todolangeditor",
  "version": "1.0.0",
  "description": "TodoLang editor",
  "scripts": {
    "start": "webpack-dev-server --hot --open",
    "antlr4ts": "antlr4ts ./TodoLangGrammar.g4 -o ./src/ANTLR"
  },
  "author": "Open",
  "license": "ISC",
  "dependencies": {
    "antlr4ts": "^0.5.0-alpha.3",
    "monaco-editor-core": "^0.18.1",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "devDependencies": {
    "@types/node": "7.0.7",
    "@types/react": "^16.8.12",
    "@types/react-dom": "^16.8.3",
    "@webpack-cli/serve": "^1.7.0",
    "antlr4ts-cli": "^0.5.0-alpha.3",
    "css-loader": "^3.3.0",
    "html-webpack-plugin": "^3.2.0",
    "style-loader": "^1.0.1",
    "ts-loader": "^5.3.3",
    "typescript": "^4.8.4",
    "webpack": "^4.29.6",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.11.1"
  }
}

我們從setupLanguage();進入修改

新增TodoLangAutoCompletionProvider2

nodejs
import * as monaco from "monaco-editor-core";
import { WorkerAccessor } from "./setup";

export default class TodoLangAutoCompletionProvider2 {
    private worker: WorkerAccessor;
    TodoLangAutoCompletionProvider2(worker: WorkerAccessor) {
        this.worker = worker;

    }
    // resolveCompletionItem?(model: editor.ITextModel, position: Position, item: CompletionItem, token: CancellationToken): ProviderResult<CompletionItem>{
    public async get_label(resource: monaco.Uri): Promise<Array<string | undefined> > {
        let suggestions = [];
        // get the worker proxy
        const worker = await this.worker(resource)
        // call the validate methode proxy from the langaueg service and get errors
        const formattedCode = await worker.get_label();
        console.log(formattedCode);

        return formattedCode;

    }

}

回到setup.ts新增

node.js
export function setupLanguage() {
    (window as any).MonacoEnvironment = {
        getWorkerUrl: function (moduleId, label) {
            if (label === languageID)
                return "./todoLangWorker.js";
            return './editor.worker.js';
        }
    }
    monaco.languages.register(languageExtensionPoint);
    monaco.languages.onLanguage(languageID, () => {
        monaco.languages.setMonarchTokensProvider(languageID, monarchLanguage);
        monaco.languages.setLanguageConfiguration(languageID, richLanguageConfiguration);
        const client = new WorkerManager();

        const worker: WorkerAccessor = (...uris: monaco.Uri[]): Promise<TodoLangWorker> => {
            return client.getLanguageServiceWorker(...uris);
        };
        //Call the errors provider
        new DiagnosticsAdapter(worker);
        let tw = new TodoLangAutoCompletionProvider2();
        tw.TodoLangAutoCompletionProvider2(worker);

        // triggerCharacters.push('C');

        // let tmp =new TodoLangAutoCompletionProvider(worker);
        monaco.languages.registerDocumentFormattingEditProvider(languageID, new TodoLangFormattingProvider(worker));
        // monaco.languages.registerCompletionItemProvider(languageID,  new TodoLangAutoCompletionProvider(worker));
        monaco.languages.registerCompletionItemProvider(languageID, {
            triggerCharacters: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'],
            provideCompletionItems: async function (model, position, context) {
                let get_label;
                let suggestions=[];
                console.log(get_label);
                console.log(context.triggerCharacter);
                get_label = await tw.get_label(model.uri);
                for (let index = 0; index < get_label.length; ++index) {
                    const element = get_label[index];
                    // console.log(element);
                    if (element != undefined)
                        if (element.indexOf(context.triggerCharacter) > 0) {

                            console.log(element);
                            suggestions.push({
                                label: element,
                                insertText: element,
                                kind: 1,
                            });
                        }
                }

                return { suggestions };
            },
        });
    });

}

export type WorkerAccessor = (...uris: monaco.Uri[]) => Promise<TodoLangWorker>;

主要透過get_label 去跟worker.languageService去拿我們的ast define name

js
    public get_label():Promise<Array<string | undefined>> {
        const code = this.getTextDocument();
        return Promise.resolve(this.languageService.get_label(code));
    }
js
import { TodoExpressionsContext, AddExpressionContext, CompleteExpressionContext } from "../ANTLR/TodoLangGrammarParser";
import { parseAndGetASTRoot, parseAndGetSyntaxErrors, parseAndGetLable } from "./parser";
import { ITodoLangError } from "./TodoLangErrorListener";

export default class TodoLangLanguageService {
    public input:string;
    validate(code: string): ITodoLangError[] {
        const syntaxErrors: ITodoLangError[] = parseAndGetSyntaxErrors(code);
        const ast: TodoExpressionsContext = parseAndGetASTRoot(code);
        const get_label: Array<string | undefined> = parseAndGetLable(code);
        // console.log(ast.children);
        // console.log(syntaxErrors);
        // console.log(get_label);

        return syntaxErrors.concat(checkSemanticRules(ast, get_label));
    }
    get_label(code: string): Array<string | undefined> {
        const get_label: Array<string | undefined> = parseAndGetLable(code);
        return  get_label;
    }

    format(code: string): string {
        // if the code contains errors, no need to format, because this way of formating the code, will remove some of the code
        // to make things simple, we only allow formatting a valide code
        if (this.validate(code).length > 0)
            return code;
        let formattedCode = "";
        const ast: TodoExpressionsContext = parseAndGetASTRoot(code);
        ast.children.forEach(node => {
            if (node instanceof AddExpressionContext) {
                // if a Add expression : ADD TODO "STRING"
                const todo = node.STRING().text;
                formattedCode += `ADD TODO ${todo}\n`;
            } else if (node instanceof CompleteExpressionContext) {
                // If a Complete expression: COMPLETE TODO "STRING"
                const todoToComplete = node.STRING().text;
                formattedCode += `COMPLETE TODO ${todoToComplete}\n`;
            }
        });
        return formattedCode;
    }
}

function checkSemanticRules(ast: TodoExpressionsContext, get_label: Array<string | undefined>): ITodoLangError[] {
    const errors: ITodoLangError[] = [];
    const definedTodos: string[] = [];
    // console.log(definedTodos);
    // errors.forEach(element => {
    //     let serach =element.input;
    //     get_label.forEach(element2 => {
    //         if(  element2.indexOf(serach) >0)
    //         console.log(element2);
    //         // some.indexOf("fmpe");  //11
    //         // some.indexOf("ldaspojpoe"); //-1
    //     });
    // });

    ast.children.forEach(node => {

        if (node instanceof AddExpressionContext) {
            // if a Add expression : ADD TODO "STRING"
            const todo = node.STRING().text;
            // console.log(todo);
            // If a TODO is defined using ADD TODO instruction, we can re-add it.
            if (definedTodos.some(todo_ => todo_ === todo)) {
                // node has everything to know the position of this expression is in the code
                errors.push({
                    code: "2",
                    endColumn: node.stop.charPositionInLine + node.stop.stopIndex - node.stop.stopIndex,
                    endLineNumber: node.stop.line,
                    message: `Todo ${todo} already defined`,
                    startColumn: node.stop.charPositionInLine,
                    startLineNumber: node.stop.line,
                    input: ""
                });
            } else {
                definedTodos.push(todo);
            }
        } else if (node instanceof CompleteExpressionContext) {
            const todoToComplete = node.STRING().text;
            // console.log(todoToComplete);
            if (definedTodos.every(todo_ => todo_ !== todoToComplete)) {
                // if the the todo is not yet defined, here we are only checking the predefined todo until this expression
                // which means the order is important
                errors.push({
                    code: "2",
                    endColumn: node.stop.charPositionInLine + node.stop.stopIndex - node.stop.stopIndex,
                    endLineNumber: node.stop.line,
                    message: `Todo ${todoToComplete} is not defined`,
                    startColumn: node.stop.charPositionInLine,
                    startLineNumber: node.stop.line,
                    input: ""
                });
            }
        }

    })
    return errors;
}
js
import { TodoLangGrammarParser, TodoExpressionsContext } from "../ANTLR/TodoLangGrammarParser";
import { TodoLangGrammarLexer } from "../ANTLR/TodoLangGrammarLexer";
import { ANTLRInputStream, CommonTokenStream } from "antlr4ts";
import TodoLangErrorListener, { ITodoLangError } from "./TodoLangErrorListener";

function parse(code: string): {ast:TodoExpressionsContext, errors: ITodoLangError[],get_lable:Array<string | undefined>} {
    const inputStream = new ANTLRInputStream(code);
    const lexer = new TodoLangGrammarLexer(inputStream);
    lexer.removeErrorListeners()
    const todoLangErrorsListner = new TodoLangErrorListener();
    lexer.addErrorListener(todoLangErrorsListner);
    const tokenStream = new CommonTokenStream(lexer);
    const parser = new TodoLangGrammarParser(tokenStream);
    parser.removeErrorListeners();
    parser.addErrorListener(todoLangErrorsListner);
    const ast =  parser.todoExpressions();
    const get_lable =parser.get_label;
    const errors: ITodoLangError[]  = todoLangErrorsListner.getErrors();
    return {ast, errors,get_lable};
}
export function parseAndGetASTRoot(code: string): TodoExpressionsContext {
    const {ast} = parse(code);
    return ast;
}
export function parseAndGetSyntaxErrors(code: string): ITodoLangError[] {
    const {errors} = parse(code);

    return errors;
}

export function parseAndGetLable(code: string):Array<string | undefined> {
    const {get_lable} = parse(code);
    return get_lable;
}

這邊TodoLangGrammarParser重寫一個fucntion出來,得到public get_label: Array 整體大概是邏輯觸發錯誤時去抓define_lable出來

registerCompletionItemProvider 每次觸發會透過TodoLangAutoCompletionProvider2撈取存在worker的lable.

這邊要注意非同步async provideCompletionItems: async function (model, position, context) 用index 抓取關鍵字也就是get_label的Array

到這邊就完成作者的自動補全。

其他功能也能正常WORK.

在稍微改裝一點我們換成vscode 風格,如果在加上前面的riscv模擬器,就可以變成線上ide 目前寫好按鈕假設,搞清楚input ouput我們就可以模擬在線上ide執行riscv指令。

https://github.com/x213212/TodoLangEditor?organization=x213212&organization=x213212