Thor-2026.08.0
2026.08.0
This is the first release of Thor. Read the README.md for more information.
ctrl + alt + l/ "Edit: Format Document" formats the active Odin file with a native in-editor formatter — no external tool required.format_on_savesetting formats an Odin file before an explicit save (off by default; never runs on autosave)..thor/odin-formatter.jsonsets per-workspace formatter options: brace style, tabs vs. spaces, alignment, import sorting, and more.- Editing: multiple cursors, editor split, find and replace, undo and redo, auto-indent, bracket and quote pairs, line comment toggle, autosave.
- Tree-sitter syntax highlighting for 45 languages, each one a plugin.
- Odin language intelligence, served in the editor with no language server: hover, go to definition, completion, signature help, code actions, find all references, rename symbol, document and workspace symbols, and compiler diagnostics. The
language_intelligencesetting turns it off per feature. - Console panel with one live shell per tab. The shell stays alive between commands, so
cdand environment variables persist.+lists the shells found on the machine: pwsh, Windows PowerShell, cmd, the MSVC developer prompt, Git Bash, MSYS2, Cygwin, WSL, nu, and the POSIX shells. - Command palette (
ctrl + .) runs every action, and quick open (ctrl + tab) finds a file by name. - Explorer panel with the file tree of the workspace, keyboard driven and watched for changes on disk.
- Each folder keeps its own session: open tabs and layout come back with it.
open_folder_inchooses whether a second folder replaces this window or opens a new one. - Lua plugins for languages, commands, panels and key bindings. Each plugin is sandboxed and asks for its permissions before it loads; a workspace can carry its own plugins in
.thor/plugins. - Settings view for themes, fonts, icon packs and plugin permissions, over
settings/settings.json,settings/keybinds.jsonand a workspace.thor/. - Named shell commands in
.thor/tasks.json, started from the titlebar or the command palette. - Rendered Markdown preview (
f4). - Help menu opens the manual in the editor or the browser, and an interactive tutorial.
- Thor asks GitHub for a newer release shortly after start, at most once a day.
check_for_updatesturns the background check off, and Help > Check for Updates asks at any time. - An update installs itself: the archive for the platform is checked against the release
SHA256SUMS, the files beside the binary are replaced, and Thor restarts into the same folder.settings/files that were edited andsessions/are kept as they are. - Dismissing an update is remembered for that version. The title bar keeps a button with the new version that brings the offer back.
- A build from source, or one in a directory Thor cannot write, reports the new version and opens the Releases page instead of installing.
thor [path]opens a folder or a file;--versionand--helpprint and exit.- Settings view redesigned: a category sidebar replaces the single scrollable list, with a search box and a General/Workspace scope switch that writes to
settings/or the workspace.thor/explicitly. - Settings has a close button, a keyboard-hint footer, and a button that empties the search box.
- The Settings window keeps one size, so a scope or category switch no longer resizes it under the cursor.
- "LSP Setup" top-bar menu: checks which servers from
settings/lsp.jsonare onPATHand adds a workspace task that installs a missing one. - Settings rows light up under the cursor, number steppers are spaced apart, and a keybinding shows its chord as a key cap.
- Clicking the Thor logo at the top left of the titlebar opens Settings.
- A welcome page opens when no folder or file is given and no past session is found:
Open Folder,Open Fileand a recent-workspaces list. The explorer, tab bar and console panel stay hidden until a workspace opens. File: Close Workspacereturns to the welcome page from an open workspace, from the File menu or the command palette.- Console panel: click and drag to select scrollback text.
ctrl + shift + ccopies the selection, or the whole scrollback with nothing selected;ctrl + vpastes into the input line. The right-click menu gained "Copy" next to "Copy All". - Language servers answer for every language other than Odin: hover, go to definition, find all references, document symbols, workspace symbols, signature help, completion, diagnostics, semantic colors, rename and code actions.
settings/lsp.jsonnames the servers, and a workspace.thor/lsp.jsonoverlays it by serverid. - A language server's errors and warnings are drawn in the editor as the Odin compiler's are, against the unsaved buffer.
clangd,rust-analyzer,gopls,basedpyright,typescript-language-server,lua-language-serverandzlsare configured out of the box. A server starts the first time a file it claims is opened, and a server that is not installed is skipped.- Workspace symbols (
ctrl + q) send the typed text to the language server as you type, instead of only filtering a fixed list. - Opening a different folder restarts its language servers against the new
settings/lsp.json/.thor/lsp.json, instead of leaving the previous folder's servers running. - Workspace symbols (
ctrl + q) on a language-server-backed file shows "Type to search workspace symbols…" instead of a blank list until you type, for servers that only answer a typed query. - A language server's progress messages ("Indexing…", with a percentage when the server reports one) show in the statusline.
- A language server can apply its own edits (
workspace/applyEdit) directly in the editor, as one undoable change. - Code actions (
ctrl + shift + u) on a language-server-backed file are scoped to the current selection, not only the caret. - Python's default language server is
basedpyright, notpyright— plain pyright never offered an "add missing import" code action. - Fixed a language server's code actions silently failing to apply to an unsaved buffer when the server spelled the file's drive letter in a different case than Thor opened it under.
- The "LSP Setup" top-bar menu is now one menu per server — "clangd Setup", "rust-analyzer Setup", "gopls Setup", "basedpyright Setup", "typescript-language-server Setup", "lua-language-server Setup" and "zls Setup" — each still checking its own server against
PATHand adding a workspace task when it is missing. - "clangd Setup" gained "Configure compile_commands.json…": detects CMake, Bazel (bzlmod) or Make and adds a workspace task to generate the compilation database clangd needs, writing a
.clangdfile for a CMake project so clangd finds its build directory. - Settings' Language category gained a "Language Servers" group: every configured server, plus the built-in Odin support, gets its own on/off switch, and each enabled one gets a foldable "Features" group of per-kind toggles. Backed by the new
language_backendssetting, and takes effect immediately, unlike editinglsp.json. - Dragging a splitter next to an open 3D model view no longer stutters.
- Opening a large image, or reloading an open image tab after an external edit, no longer freezes the editor while it decodes.
- Fixed an image or 3D model tab's loading spinner spinning forever instead of clearing once it finished loading.
- The editor placeholder under a loading image tab now reads "Loading image..." instead of jumping straight to "Image".
- Quick open (
ctrl + tab) and the command palette's file list are now instant on a large workspace, warmed in the background instead of walked on every open. ctrl + alt + l/ "Edit: Format Document" now formats any file whose configured language server advertises formatting support (clangd, gopls, rust-analyzer, basedpyright, etc.), not only.odinfiles.- New
ctrl + alt + shift + l/ "Edit: Format Selection" formats the selected lines through the language server; with nothing selected it formats the whole document. - New
format_on_typesetting reformats a line as you type a trigger character the language server asks for, such as}or a newline. Off by default, next to the existingformat_on_save. format_on_savenow also formats non-Odin files served by a formatting-capable language server, not only Odin.- Settings > Language > a server's Features group gained "Format Selection" and "Format On Type" toggles next to "Format Document".
- The completion popup takes the mouse: a click accepts a candidate, hovering one highlights it, and the wheel over the popup walks the list instead of scrolling the text.
- The console prompt is a full input line:
left/right/home/end/deletemove and edit the caret, a click places it, andctrl + vpastes at the caret. - Links in the Markdown preview are clickable. A URL opens in the browser; a path relative to the document opens as a tab, so the manual's own pages link to each other.
- Find and replace gained three toggles:
Aa/alt + cmatches case,W/alt + wmatches whole words, and.*/alt + rreads the query as a regular expression. Each stays set until Thor restarts. - Replace All is one undo step, and with
.*on it rewrites matches of different lengths in one sweep. A pattern that does not compile reads "Invalid pattern" where the match count sits. - The explorer has a scrollbar; drag the thumb to move through a long file tree.
- The status bar reports the indentation the file actually uses, tabs included, instead of always claiming spaces.
- The status bar names any language a plugin registers, including extensionless ones such as
DockerfileandMakefile, instead of falling back to "Plain Text". - Undo and Redo sit at the top of the Edit menu and the editor's right-click menu, greyed out when there is nothing to move, and answer to "Edit: Undo" / "Edit: Redo" in the command palette.
ctrl + shift + zandctrl + yput back a rename or code action that changed several files, across all of them, matching the undo that already took the whole set back.thor a.odin b.odinopens every path given, in order, with the last one active. The first folder becomes the workspace and a later one is ignored. A--helpor--versionflag now wins wherever it stands.- Tree-sitter highlight rules guarded by
#match?,#lua-match?or#has-parent?apply instead of being dropped: Odin constants, macros and type names, C constants, and the same rules across the other grammars that ship them. - JSON, YAML, INI, Makefile, Dockerfile, batch,
.envand.gitignorefiles highlight only the lines the pane shows, so a large one keeps up with typing. - A file colored by a pure-Lua lexer is no longer re-read whole every time the view scrolls.
- Plugin API:
line_based = trueinthor.register_languagemarks a lexer that reads each line on its own, andthor.ts.parsereuses the tree the plugin parsed last, so re-parsing an edited buffer costs the edit. - The Odin formatter wraps a call, a procedure parameter list or a composite literal that crosses
character_width, one item per line with a trailing comma. A list holding an argument that spans lines, such as a procedure literal, still hugs its call. sort_importssorts each run ofimportdeclarations,base:,core:andvendor:paths first. A run with a comment in it keeps the order it was written.align_struct_fieldslines up a struct's field types, andalign_struct_valuesthe=of enum members and multi-line composite literals and a bit field's|. Both are on by default. A blank line or an own-line comment starts a new column.align_constant_definitionsandalign_struct_declarationsline up the::of consecutive declarations. Both off by default.space_single_line_blockskeeps a one-statement block the source wrote on one line as{ stmt }.ctrl + alt + shift + l/ "Edit: Format Selection" works on Odin files instead of reporting that formatting is unavailable. It reformats only the lines selected.format_on_typereindents the current line of an Odin file when}is typed.- Formatting an Odin file with no folder open uses the shipped defaults instead of no indent at all.
- Hover and completion work on a by-reference loop variable:
p.insidefor &p in pointsoffers the element's fields. - The workspace symbol picker (
ctrl + q) filters as you type in the analyzer instead of sending every symbol in the workspace on each keystroke. - A code action a language server runs itself, rather than answering with edits, is offered and runs. Its row reads "runs on the server", and the server's changes arrive as it applies them.
- Fixed a crash when a plugin calls
tree:language()ortree:source()on a tree that was already collected. - On Linux and macOS, two paths that differ only in case are no longer taken for the same file.
- A first run no longer warns about the settings files it has not created yet.
- Typing an opening bracket or quote no longer rebuilds the whole document, so auto-pairs stay fast in a large file.
- A tab is drawn at a real tab stop,
tab_widthcolumns wide, instead of one space. The caret column, soft tabs, "Edit: Align at Character", soft wrap and the status bar all count it the same way. - Upper/lower/capitalize (
alt + u,alt + l,alt + c) work in every script, not only ASCII:straßeuppercases toSTRAẞE, and Greek, Cyrillic and accented Latin change case. - Case-insensitive find matches across case mappings that change a word's length, so
straßefindsSTRAẞE. - The caret, backspace and delete move by one character as it is displayed: an emoji, a flag, a Hangul syllable or a letter with a combining accent is no longer split in half. This covers the editor, the console and the find and command-palette input lines.
ctrl + dmatches whole words, sofoono longer selects thefooinsidefood.ctrl + shift + dkeeps the old substring behavior.- Repeated
ctrl + dreaches every occurrence in the file. It used to stop once it wrapped to the first one. - Copy with several carets and nothing selected copies every caret's line, matching what cut already deleted.
- On Linux and macOS, the Git menu no longer writes a stray
nulfile into the workspace. - On Windows, the explorer keeps up with the disk after a watch failure: the workspace is rescanned on an interval instead of updates stopping until restart.
- The file tree, not only the git status, is refreshed when the watcher reports that it lost events.
- On Linux, changes under a folder renamed inside the workspace are reported at its new path.
- On Linux and macOS, a plugin command that passes its time budget is stopped whole: a child it left in the background no longer keeps running after it.
- On Linux and macOS, a command run by a plugin or the Git menu ends at a closed pipe again once a terminal or a language server has started.
- On Linux, a workspace larger than the system watch limit is rescanned on an interval instead of losing changes in the part of the tree that was left unwatched.
- On Linux,
node_modulesand git's object store are no longer watched, matching macOS. Changes inside them are not reported; the directories themselves still are. - A key chord can name the Command key:
cmd(alsocommand,super,metaorwin) insettings/keybinds.json, so macOS bindings such ascmd+sare possible.optionis accepted as a second spelling ofalt. - Plugin API: the table
thor.on_keyreceives carries acmdfield next toctrl,shiftandalt. - On Windows on ARM, the "Developer Command Prompt" shell and the dependency build load the ARM64 MSVC tools instead of the x64 ones, and an installation carrying only the ARM64 toolset is found.
- On Linux and macOS, opening a folder that is already open brings that window to the front, through
xdotool,wmctrlorosascript. When none of them can, the status bar says the window could not be raised instead of only naming it. - On Linux and the BSDs, "File: Reveal in File Explorer" selects the file in the file manager instead of only opening its folder, as it already did on Windows and macOS. Nautilus, Dolphin, Nemo, Thunar, PCManFM-Qt and Caja answer; a file manager that does not still gets the folder opened.
- On Linux and macOS, key chords follow the keyboard layout, as they already did on Windows: on QWERTZ,
ctrl + zundoes on the key labeled Z. Digit and punctuation chords follow the layout too, on every platform. - A held key repeats, so holding
ctrl + vpastes more than once. The shortcuts that open or toggle something still act once per press. - Ligatures and right-to-left text are drawn at every font size. Zooming to a size the font manifest does not name no longer turned them off, and the tab strip and status bar get them as well.
AltGrwith the mouse wheel scrolls the editor instead of zooming it.- The hover popup closes when the pointer leaves the editor, instead of staying up over the text it no longer points at.
- Plugin API:
thor.on_key_up(fn)(permissionkeys) reports key releases, with the tablethor.on_keyreceives. - A relative line jump takes a count of more than one digit: the digits typed while
altis held make one number, so a gutter distance of 10 or more is reachable. The jump runs whenaltis released, and the status bar shows the count while it is typed. - A drag holds its target when a second mouse button is pressed during it: a splitter, scrollbar, tab or file being dragged no longer sticks to the cursor after the right button is clicked mid-drag.
- The console keeps its scroll position while a command writes output and when it ends, so scrollback stays where it is put instead of jumping to the last line. Submitting a command returns to the bottom.
- A picker opened after workspace symbols (
ctrl + q) keeps its own rows: code actions and "Go to symbol" are no longer replaced by symbol results as you type. - A folder the explorer cannot read is named in the error color instead of reading as empty, and the next open reads it again.
- On Linux and macOS, the explorer git colors and the gutter diff bar appear, as they already did on Windows.
- On Windows, a file git spells in a different case than the filesystem does still gets its explorer color and gutter diff.
- A
git worktreecheckout, a submodule and a subfolder of a repository opened as the workspace are read as git repositories: the branch name, the explorer colors and the gutter diff work in all three instead of reading as no repository. - The editor's right-click menu acts on the pane that was clicked, not always the first one. Find and replace (
ctrl + f) and Go to Line search and scroll the focused pane too. - Deleting a file no longer races an autosave of it: the save is stopped first, so the file cannot come back after the delete. Deleting a folder closes the tabs of the files inside it.