Dawid Moczadło

Inputs with IntelliSense and Syntax Highlighting in Monaco Editor (React)

June 14, 2024 (6m ago)1,011 views

TL;DR; The article is about building IntelliSense and Syntax Highlighting for custom DSL in Monaco Editor (React).

If you are reading this you probably made the mistake of creating your own DSL (Domain specific language).

You thought it is a good idea to make your users learn new syntax just to use your product. I am not judging, I did the same.

This blog is a summary of my efforts to unfuck the experience for my users. You can flatten the learning curve of DSL by introducing some technical debt. It is not a solution, it is a tradeof.

It was built using:

If you hate technical debt and hacky solutions, do not read it.

What you can expect

Playground input
Example of hovers, completions and validation of custom DSL

Note, not everything works perfectly, but it is a good start.

I won't give you copy-paste solution, my code sucks. I will show you what is possible with a little bit of work. I will set the bar for you.

You ove your users a decent experience, experience that does not require having a PHD in your DSL. Users do not want to learn and remember your syntax, they want to get the job done.

Examples

Monaco editor can be rendered as a single input element, without sacrificing any of its features. It can be used for inline editing, with syntax highlighting, IntelliSense, and validation.

Custom DSL
With placeholder
Read only
With errors
Javascript
Example of hovers, completions and validation of custom DSL

How it works technically

The big problem is that we do not want to write LSP (Language Server) for our DSL. We want to deliver decent experience, fast.

The solution is to use expression evaluator for your DSL that will try evaluating the expression with stubs and return errors if something is wrong. You can even implement type checks for my DSL! In my case the evaluator is written in Go and I had to compile it to Javascript using gopherjs. So it is better to have it in JS or TS.

Pseudo code

Each time user changes the input, we evaluate the expression and display the errors as markers in the editor. It is not a LSP, but for this case it approximates the experience.

1const expr = `contains(BasePath, "foo") == true`; 2 3const stubVariables = { 4 // variable name corresponds to its type 5 BasePath: "string", 6 RootURL: "string", 7 Hostname: "string", 8 Time: "number", 9 // ... 10}; 11 12const stubFunctions = { 13 md5: validate("md5", ["string"], "string"), 14 sha256: validate("sha256", ["string"], "string"), 15 sha1: validate("sha1", ["string"], "string"), 16 mmh3: validate("mmh3", ["string"], "string"), 17 contains: validate("contains", ["string", "string"], true), 18}; 19 20const errors = evaluateExpression(expr, { 21 ...stubVariables, 22 ...stubFunctions, 23}); 24

validate is higher order function that returns a function that will validate the arguments and return the result.

Making Monaco Editor behave as in-line input

You can just copy-paste the source code and it will work. It is not perfect, but it is a good start.

Caveats:

Example usage:

1import { useMonaco } from "@monaco-editor/react"; 2import { EditorSmallInput } from "..."; 3 4const Component = () => { 5 const monaco = useMonaco(); 6 7 // language ID should be unique per instance of component 8 const languageId = "customDSL"; 9 10 return ( 11 <EditorSmallInput 12 monaco={monaco} 13 languageId={languageId} 14 placeholder="Hello, I am your placeholder!" 15 value={"initial_value_here"} 16 /> 17 ); 18}; 19

My code is not limited to single line inputs, it can be extended to multi-line inputs as well.

You can change the number of lines by changing the constant in the code.

1// ... 2const MAX_LINES = 1; 3// ... 4

Your own "IntelliSense"

Check out source code for IntelliSense of my DSL.

It has set of completions, hovers and validations. You can edit it to fit your needs.

Final;

I hope this article will help you to make your DSL more user friendly. It is not a perfect solution, but it works.

If you would like to see more examples or have any questions, feel free to reach out to me at @kannthu1.

🚀 We are hiring

Founding Backend Engineer

Here is what you will do:

  • Build state-of-the art code scanning tools (based on LLMs)
  • Integrate LLMs and build RAG pipelines
  • Build parsers to represent code as graph
  • Optimize scanning pipeline to work near realtime
  • Scale to millions of snippets of code

Contact me at ZGF3aWRAdmlkb2NzZWN1cml0eS5jb20= (figure it out yourself)