Skip to content
Brainstorms

Thoth: An IDE That Grows With You

An idea for an IDE that starts as a lightweight editor and grows through built-in modules instead of extensions, with a VS Code-like window and a LunarVim-like terminal mode, and a suggested technology stack to build it for Linux and Windows.

An IDE that adapts to your exact needs, nothing more, nothing less.

Features

  • Starts like a simple and lightweight code editor and the user can activate modules to add functionalities to fill their needs. For example a .NET module, an Angular module etc
  • Experience must be similar to Visual Studio Code on Linux and Windows and similar to LunarVim on terminal
  • Do not support extensions, the idea is to use the modules to extend the application on-demand

Platforms

  • Linux
  • Windows

First release modules

  • .NET
  • Angular
  • React
  • Flutter
  • Python
  • Rust
  • HTML/CSS
  • JavaScript/TypeScript
  • Java

Technology stack

The feature that shapes the stack most is the second one: the same IDE in a window and in a terminal. If the window and the terminal are two programs, every feature is built twice. So the design starts from one core that owns everything an editor does, such as buffers, cursors, syntax trees, language servers, commands, keymaps and modules, and two thin front ends that only draw what the core describes and send it the user’s input. The core and the front ends talk only through typed messages: input events go in, view updates come out. Neither side calls into the other, so a front end can be replaced, and the core could later run in a separate process, for example on a remote machine over SSH.

txt
              modules: .NET, Angular, ...
                       │
                ┌──────┴───────┐
                │  thoth-core  │
                └──────┬───────┘
          input ▲      │      ▼ view
          ┌────────────┴────────────┐
   ┌──────┴──────┐           ┌──────┴──────┐
   │  thoth-gui  │           │ thoth-term  │
   │ VS Code-like│           │LunarVim-like│
   └─────────────┘           └─────────────┘

The two experiences then differ where they should, in drawing and in keymaps. By default the window uses VS Code’s shortcuts, mouse and panels, and the terminal uses modal, Vim-style editing with a leader key, as LunarVim does on top of Neovim. Both keymaps are profiles in the core rather than features of a front end, so the user can choose either one in each front end: Vim keys in the window, or VS Code keys in the terminal.

The suggestion at a glance

ConcernChoiceWhy
LanguageRustFast start-up and low memory with no runtime, and the language the editors closest to this idea are written in: Helix, Zed and Lapce.
Async runtimeTokioLanguage servers, debug adapters, builds and searches all run in the background, and the core waits on all of them at once.
Core and front endsTyped messages over channelsInput events in, view updates out, so both front ends share one core and neither depends on the other’s libraries.
Text bufferRopeyA rope keeps edits fast in large files and makes undo history cheap.
SyntaxTree-sitterIncremental parsing for highlighting, folding, indentation and selecting by syntax node, from one grammar per language.
Language featuresLanguage Server ProtocolCompletion, diagnostics, go to definition and refactoring come from each language’s own server, so a module wraps a server instead of reimplementing a language.
DebuggingDebug Adapter ProtocolThe same idea for debuggers: one client in the core, one adapter per language.
Terminal front endratatui with crosstermThe standard Rust terminal UI library, and crossterm drives both Linux terminals and the Windows console.
Window front endGPUIThe GPU UI framework Zed is built on, made for a code editor, with fast text rendering; it runs on Windows and Linux.
ModulesRust crates with declarative manifestsA manifest says what a module handles before any of its code runs, so modules activate on demand.
Search and filesignore, grep-searcher, nucleoThe crates behind ripgrep for walking and searching the workspace, and Helix’s fuzzy matcher for the file and command pickers.
File watchingnotifyCross-platform file system events, so files changed outside Thoth, by Git or a build, are reloaded.
Gitgitoxide (gix)A Git implementation in Rust, without a C library to build and ship.
Built-in terminal panelalacritty_terminal with portable-ptyThe terminal emulator Zed embeds, and a pseudo-terminal that works on Windows too.
ConfigurationTOMLOne file for settings, keymaps and which modules are active.
Testingcargo test with instaSnapshot tests of what the core renders, using ratatui’s test backend for the terminal.
Packagingcargo-distMSI and a winget manifest for Windows; archives, .deb and AppImage for Linux.

Why Rust

“Lightweight” is a requirement, not a wish, so the core should start in milliseconds and stay small with nothing open. Rust gives that without a garbage collector or a runtime to start, and it is the only ecosystem where every piece above already exists as a library and has been proven in a shipping editor. Helix is the closest reference: a terminal editor in Rust with Tree-sitter and language servers built in, and no plugin system, which is Thoth’s stance on extensions too.

OptionWindowTerminalWeightTrade-off
Rust core + GPUI + ratatuiGPUIratatuiLightestRust has a steep start, and GPUI changes along with Zed and is documented mostly by Zed’s own code.
.NET core + Avalonia + Terminal.GuiAvalonia with AvaloniaEditTerminal.GuiLight with Native AOT; see How C# RunsFamiliar language, and the .NET module could use Roslyn directly, but Tree-sitter is only reachable through native bindings, and the two UI libraries each bring their own text model.
Tauri or Electron with MonacoMonaco, VS Code’s editorA second editorHeaviestThe window would look like VS Code quickly, but Monaco owns the text model, so the terminal mode would be another editor with every feature built twice.
A Neovim distributionA Neovim GUI such as NeovideNeovimLightThe fastest route to the terminal half, but modules become Lua plugins, which is the extension system Thoth wants to avoid, and the window would feel like Vim, not VS Code.

If Rust is too much to learn alongside building an IDE, the .NET row is the honest second choice: the architecture stays the same, one core and two front ends, only the libraries change.

Why GPUI

Most Rust UI libraries are general purpose, so an editor built on them draws its text area by hand on a canvas. GPUI was written for Zed, a code editor, so fast text rendering, many panes and an editor-style layout are what it does best, which is what a VS Code-like window needs. It comes with three costs:

  • Its API moves with Zed. There is little documentation outside Zed’s own code, so Thoth should pin one version and plan its upgrades.
  • Zed’s editor is not GPUI. GPUI is licensed under Apache 2.0, but most of Zed’s other crates, its editor included, are GPL. Reading them is fine; copying code from them would make Thoth GPL too.
  • GPUI has its own async executors. The core runs on Tokio and never depends on GPUI; the window front end receives the core’s messages and turns them into GPUI views. This is also what keeps the terminal build free of GPUI.

Modules instead of extensions

A module is first-party code compiled into Thoth, not a package downloaded from a marketplace, and Thoth never loads modules from outside its own binary. That does not close the set of languages: Thoth is open source, so anyone can build a new module or change an existing one and open a pull request. Once it is reviewed and merged, it ships with the next release, tested with every other module. Inactive modules cost nothing at run time: they register no commands, start no processes and add no menu items. Activating one does three things, each driven by the module’s manifest:

  1. Fetches its tools, such as a language server or a debug adapter, into Thoth’s own data folder, pinned to a version Thoth was tested with. This is what mason.nvim does for LunarVim.
  2. Starts its servers when a file it handles is opened, and stops them when the last one closes.
  3. Registers its commands, keymaps and panels with the core, such as “Run tests” or a project tree.

Each module is a Rust crate with two parts. A declarative manifest in TOML lists what the module handles and needs: the file types and project files that activate it, the language servers and debug adapters with their versions, and the commands it adds. The core reads every manifest at start-up without running any module code, so it knows when to offer or activate a module at no cost. The crate itself implements a Module trait for the behaviour a manifest cannot describe, such as parsing a solution file, so the core stays ignorant of every language. Because the set of modules is fixed at build time, the core can test every combination, which a marketplace never can.

toml
[module]
id = "dotnet"
name = ".NET"

[activation]
files = ["*.cs", "*.csproj", "*.sln", "*.slnx"]

[[language-server]]
id = "roslyn"
languages = ["csharp"]

[[debug-adapter]]
id = "netcoredbg"

[[command]]
id = "dotnet.test"
title = "Run tests"

A manifest can also require other modules. Angular and React are built on the web, so their manifests require the JavaScript/TypeScript and HTML/CSS modules instead of bringing their own copies of those servers:

toml
[module]
id = "angular"
name = "Angular"
requires = ["javascript-typescript", "html-css"]

The nine modules of the first release would start as:

ModuleLanguage serverDebuggerTasks
.NETThe Roslyn language server, or csharp-lsnetcoredbgdotnet build, dotnet test, dotnet new, solution and project tree
AngularAngular Language Service, on top of JavaScript/TypeScript and HTML/CSSjs-debug, from JavaScript/TypeScriptng serve, ng test, ng generate; see What Is Angular?
ReactNone of its own: JSX and TSX are handled by the TypeScript server from JavaScript/TypeScriptjs-debug, from JavaScript/TypeScriptThe project’s dev, build and test scripts, component snippets; see What Is React?
FlutterThe Dart analysis server, run as dart language-serverThe Dart and Flutter debug adapters, which include hot reloadflutter run, flutter test, device picker; see What Is Flutter?
PythonPyright for types, Ruff for linting and formattingdebugpyVirtual environment picker, pytest
Rustrust-analyzerCodeLLDBcargo build, cargo test, cargo run, Clippy
HTML/CSSThe HTML and CSS language servers extracted from VS CodeNone, debugging runs in the browserEmmet abbreviations, colour previews
JavaScript/TypeScriptA TypeScript language server, and ESLint’sjs-debug, for Node.js, Chrome and Edgenpm, pnpm and Yarn scripts, package.json awareness
JavaEclipse JDT Language Serverjava-debug, which runs inside the JDT Language ServerMaven and Gradle tasks, test runner

Node.js and Java

Several of these servers are programs for another runtime: the TypeScript, Angular, HTML, CSS, ESLint and Pyright servers run on Node.js, and the JDT Language Server runs on Java. A manifest declares the runtime its servers need, and when a module that needs one is activated, Thoth looks for it on the machine first:

  • The runtime is installed. Thoth asks the user whether to use the installed Node.js or Java, or to install its own copy for Thoth alone. If the installed version is older than the module needs, the prompt says so.
  • The runtime is not installed. Thoth tells the user that the module needs it, and that it will be installed into Thoth’s own data folder, where it changes nothing else on the machine.

The answer is saved in the configuration, so each runtime is asked about once, not once per module, and it can be changed later in the settings.

Building toward the first release

The first release ships both front ends, the window and the terminal, with all nine modules. That is a lot to build at once, so the order of the milestones before it matters:

  1. The core and the terminal front end with one module: open a folder, edit with Vim keys, find files, search, and get completion and diagnostics for C#. The terminal is the quicker front end to build and the easier one to test, and it proves the core’s design before the window is started.
  2. The window front end on the same core, with VS Code keys, which proves that the core really is shared.
  3. The other modules, starting with JavaScript/TypeScript and HTML/CSS, since Angular and React are built on them.

Decisions

The questions this brainstorm raised, and how they were settled:

  • The first release ships both front ends. The window is a requirement, not a later addition to a terminal IDE.
  • Keymaps are the user’s choice in each front end. Vim keys are the default in the terminal and VS Code keys the default in the window.
  • Modules build on other modules. A manifest can require other modules, so Angular and React build on the JavaScript/TypeScript and HTML/CSS modules.
  • Thoth only uses its own modules. New or changed modules come in as pull requests to the open-source project, not as extensions loaded at run time.
  • Runtimes are asked about once. An installed Node.js or Java is offered to the user; a missing one is explained and installed for Thoth alone.