The font atlas loader read the bundled Noto Sans Mono from a cwd-relative path with system-font fallbacks, so launching the editor from outside the repo root could pick a different font or fail. The bundled font is now embedded with #load; EditorMono.ttf remains an optional on-disk override, and the system fallback paths are gone. Together with the embedded shaders this makes the binary fully self-contained. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.6 KiB
Native Kotlin Editor Prototype
This is the first slice of an Odin-native editor architecture backed by a small Kotlin/JVM daemon.
The boundary is intentionally simple: newline-delimited JSON over localhost TCP.
The native client tracks daemon request ids for interactive requests so concurrent popups and panels consume only their matching responses.
Text edits are debounced before sending text/change to the daemon; file switches, saves, navigation, and daemon-backed requests flush the active buffer immediately.
Layout
client/odin/- Odin client smoke test for the future native editor.daemon/- Kotlin/JVM TCP daemon for Gradle/Kotlin intelligence.protocol.md- current TCP message shape.
Run
Start the daemon:
gradle -p daemon run
It prints a line like:
PORT 49321
In another terminal, run the Odin client with that port:
odin run client/odin -- 49321 /path/to/workspace
The client sends ping and workspace/open requests and prints daemon responses.
Run the SDL editor window:
odin run client/odin -- --sdl /path/to/workspace /path/to/file.kt
The SDL mode starts the daemon automatically when no port is passed. You can still connect to an already-running daemon by passing its port before the file path:
Auto-started daemons are restarted automatically if the process exits or the TCP connection drops. Explicit-port mode does not restart external daemons.
odin run client/odin -- --sdl /path/to/workspace <port> /path/to/file.kt
The file path is optional. If no file is passed, the editor restores the last open files, active tab, and cursor positions for the workspace. If there is no saved state, it opens the current prototype default file under the workspace.
SDL mode shows a simple workspace file tree in the left sidebar. Click a file to open or focus it.
Open files appear in the editor tab strip; click a tab to switch active buffers or drag tabs to reorder them.
Use Ctrl+W to close the active buffer, or click a tab's x. Dirty buffers are not closed until saved.
SDL mode creates an SDL3 GPUDevice and uses a direct SDL3 GPU command-buffer renderer when a GPU backend is available. It batches editor rectangles, selections, cursor lines, panels, and stb_truetype font-atlas text into GPU vertices. It falls back to the regular SDL renderer in unsupported environments such as SDL_VIDEODRIVER=dummy smoke tests.
The bundled Noto Sans Mono font is embedded into the client binary at build time, so the editor always has a working monospace font. Place a client/odin/assets/fonts/EditorMono.ttf file to override it.
The GPU editor path includes lightweight Kotlin/Java syntax highlighting for comments, block comments, strings, character literals, triple-quoted Kotlin strings, keywords, numbers, and type-like identifiers.
Kotlin and Java diagnostics are shown in the gutter/status area with active-file error/warning/info counts, and as red dashed underlines in the GPU editor path. Kotlin diagnostics include Java source files from the same Gradle source roots for mixed-source projects, and unsaved Kotlin/Java buffers are compiled under their declared package path.
Use F8 and Shift+F8 to move to the next/previous diagnostic in the active file.
Use Ctrl+Shift+M to toggle the diagnostics panel. Use Up/Down, PageUp/PageDown, Home/End, mouse wheel, Enter, or row clicks to navigate issues.
Use Ctrl+Shift+P to open the command palette. Type to filter commands, use Up/Down, PageUp/PageDown, Home/End, mouse wheel, Enter, or row clicks to run actions. The palette includes Reload Workspace, which refreshes the file tree, reopens the Gradle workspace, clears stale diagnostics, and resyncs the active file.
Gradle workspace import records module source roots, test source roots, resource roots, classpath entries, tasks, and common generated source directories when they already exist under build/generated/. Diagnostics also include existing module output directories from build/classes/ and build/resources/ on their compiler classpath.
Use Ctrl+Shift+T to toggle Gradle tasks in the right sidebar. Use Up/Down, PageUp/PageDown, Home/End, mouse wheel, or row clicks to select tasks, and Enter to run the selected task; the sidebar shows the loaded task count and reports started/finished/failed events, the final response, and recent stdout/stderr output.
The SDL3 GPU shaders are compiled to SPIR-V under client/odin/shaders/compiled/ and embedded into the client binary at build time, so the editor has no runtime shader files to find. The compiled files are committed; after editing the GLSL sources, regenerate them with:
scripts/compile-shaders.sh
The script uses glslc from shaderc. On Fedora, install it with sudo dnf install glslc.
Run autonomous opencode continuation loops with:
scripts/autopilot-opencode.sh
By default it runs opencode run --dir "$PWD" --continue --dangerously-skip-permissions repeatedly with an instruction to keep working through the task list and choose new tasks when the list is complete. Configure it with environment variables, for example:
OPENCODE_SESSION=<session-id> AUTOPILOT_MAX_RUNS=5 scripts/autopilot-opencode.sh
Create .opencode-autopilot-stop to stop the loop cleanly between iterations.
Set AUTOPILOT_ALLOW_PERMISSIONS=0 to disable automatic permission approval.
The editor supports basic editing keys: arrows, Ctrl+Left/Ctrl+Right word movement, PageUp/PageDown, Home/End, Shift+movement selection, Backspace, Delete, Tab as four spaces, Shift+Tab outdent, auto-indent on Enter, mouse click/drag cursor selection, Ctrl+C/Ctrl+X/Ctrl+V, Ctrl+F find, Ctrl+Z/Ctrl+Y undo/redo, Ctrl+S save, and Ctrl+Shift+S save all. Replacing a selection with typed text, Enter, Tab, or a completion is one undo step, and undo/redo updates the dirty marker when returning to the saved version. Use Ctrl+Shift+E to toggle the Explorer sidebar. The editor and sidebars scroll independently based on mouse position.
Window size, sidebar widths, sidebar visibility, open files, active tab, and cursor positions are saved under the user cache directory and restored on startup.
In the find panel, Enter finds next and Shift+Enter finds previous. Visible matches are highlighted in the GPU editor path.
Press Ctrl+M in SDL GPU mode to toggle a font metrics overlay for debugging cursor/text alignment.
Press Ctrl+Space to open the completion popup. It sends kotlin/completion to the daemon and renders returned item labels with kind hints. Use Up/Down, PageUp/PageDown, Home/End, mouse wheel, Enter, or row clicks to choose and apply a completion by replacing the identifier prefix before the cursor. Completion returns Kotlin/Java keywords plus heuristic identifiers/declarations from the current buffer and workspace.
Press Ctrl+H to request hover information at the cursor. Hover describes language keywords and shows heuristic Kotlin/Java declaration lines/locations for identifiers when found in the current buffer or workspace.
Press Ctrl+B to go to definition. Definition currently uses a heuristic source scan for simple Kotlin/Java declarations and includes the current unsaved buffer.
Use Alt+Left and Alt+Right to navigate backward and forward through definition jumps.
Press Ctrl+R to find references for the identifier at the cursor. References currently use a heuristic source scan and are shown in a popup; use Up/Down, PageUp/PageDown, Home/End, mouse wheel, Enter, or row clicks to jump to a result.
Press Ctrl+Shift+R to open the rename preview panel. Enter a new name and press Enter to request preview edits. Rename remains preview-only and does not modify files yet.