Skip to content

Instantly share code, notes, and snippets.

@kubukoz
Last active November 11, 2023 10:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kubukoz/5779d7d275e2c2241a1b2535235cf3a2 to your computer and use it in GitHub Desktop.
Save kubukoz/5779d7d275e2c2241a1b2535235cf3a2 to your computer and use it in GitHub Desktop.
Let's build an IDE!

Jakub Kozłowski | Scala in the city | 26 X 2023

Code | Recording


Why build an IDE?

  1. For fun
  2. For your proprietary DSL
  3. So that you have a talk topic

What are our options?

UI

  • Write your own frontend?
  • Monaco?
  • Existing editor?

Analysis

  • Just syntax
  • "Best effort" semantic analysis
  • Full-blown analysis

We'll use Monaco and communicate with a compiler.


The problem(s)

  • Each editor has its own extension API
  • Editors may require special environments (e.g. JVM or Node)
    • Process boundaries must sometimes be crossed

This is manageable...

┌──────────┐     ┌───────────┐
│ Your IDE │ ──> │ Your lang │
└──────────┘     └───────────┘

This is not

┌─────────┐     ┌──────────────────────┐     ┌──────────┐
│         │ ──> │        Scala         │ <── │          │
│         │     └──────────────────────┘     │          │
│         │                          ∧       │          │
│         │                          │       │          │
│         │                          │       │          │
│         │     ┌────────────┐       │       │          │
│ VS Code │ ──> │ Your lang  │ <─────┼────── │ Your IDE │
│         │     └────────────┘       │       │          │
│         │               ∧          │       │          │
│         │               │          │       │          │
│         │               │          │       │          │
│         │     ┌──────┐  │          │       │          │
│         │ ──> │ Rust │ <┼──────────┼────── │          │
└─────────┘     └──────┘  │          │       └──────────┘
                  ∧       │          │
                  │       │          │
                  │       │          │
                ┌───────────────────────────┐
                │          Neovim           │
                └───────────────────────────┘


There must be a better way

┌──────────┐     ┌───────────┐     ┌───────────┐
│ VS Code  │ ──> │           │ ──> │   Scala   │
└──────────┘     │           │     └───────────┘
┌──────────┐     │           │     ┌───────────┐
│ Your IDE │ ──> │ Something │ ──> │   Rust    │
└──────────┘     │           │     └───────────┘
┌──────────┐     │           │     ┌───────────┐
│  Neovim  │ ──> │           │ ──> │ Your lang │
└──────────┘     └───────────┘     └───────────┘

This is LSP!


LSP in 10 seconds

1. Initialize connection

┌────────┐  initialize request    ┌────────┐
│ Client │ ─────────────────────> │ Server │
└────────┘                        └────────┘
       ∧   initialize response      │
       └────────────────────────────┘

LSP in 10 seconds

2. Send notifications

┌────────┐  textDocument/didOpen notification     ┌────────┐
│        │ ─────────────────────────────────────> │        │
│        │                                        │        │
│        │  textDocument/didChange notification   │        │
│        │ ─────────────────────────────────────> │        │
│        │                                        │        │
│        │  textDocument/didChange notification   │        │
│        │ ─────────────────────────────────────> │        │
│ Client │                                        │ Server │
│        │  ...                                   │        │
│        │ ─────────────────────────────────────> │        │
│        │                                        │        │
│        │  textDocument/didClose notification    │        │
│        │ ─────────────────────────────────────> │        │
│        │                                        │        │
│        │  window/showMessage notification       │        │
│        │ <───────────────────────────────────── │        │
└────────┘                                        └────────┘

LSP in 10 seconds

2.5. Send notifications and requests

┌────────┐  textDocument/definition request    ┌────────┐
│        │ ──────────────────────────────────> │        │
│        │                                     │        │
│        │  textDocument/definition response   │        │
│        │ <────────────────────────────────── │        │
│        │                                     │        │
│        │  textDocument/diagnostic request    │        │
│        │ ──────────────────────────────────> │        │
│ Client │                                     │ Server │
│        │  textDocument/diagnostic response   │        │
│        │ <────────────────────────────────── │        │
│        │                                     │        │
│        │  workspace/configuration request    │        │
│        │ <────────────────────────────────── │        │
│        │                                     │        │
│        │  workspace/configuration response   │        │
│        │ ──────────────────────────────────> │        │
└────────┘                                     └────────┘

LSP in 10 seconds

Request

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "textDocument/definition",
  "params": {
    "textDocument": { "uri": "file:///workspace/demo.bad" },
    "position": { "line": 5, "character": 5 }
  }
}

Response

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "uri": "file:///workspace/demo.bad",
    "range": {
      "start": { "line": 2, "character": 4 },
      "end": { "line": 2, "character": 5 }
    }
  }
}

Let's get to work!

Code: kubukoz/badlang @ smol branch (full version at main)


Go to definition

textDocument/definition: (URI, position) => (URI, range)

What we need to do:

  1. Load and parse the file at uri
  2. Find out what node in the tree the cursor (position) is at
  3. Ensure that node is a reference
  4. Find the first appearance of the referenced variable
  5. Return the variable's range

Thank you

  • Slides: this file
  • Code

Get in touch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment