A lightweight terminal IDE built with Rust and ratatui. File tree, tabbed editing, split panes, LSP, syntax highlighting, code folding, git diff view, minimap, project search, 32 themes, and customizable keybindings — all in a single binary.
A full IDE experience anywhere you have a terminal. SSH into a server, run lazyide, and pair with your agentic coding tool.
Linux / macOS / WSL2:
curl -fsSL https://tysonlabs.dev/lazyide/install.sh | shWindows (PowerShell):
irm https://tysonlabs.dev/lazyide/install.ps1 | iexMore install options
Homebrew (macOS):
brew tap TysonLabs/tap && brew trust TysonLabs/tap && brew install lazyideHomebrew 7 refuses formulae from third-party taps until you trust them once; brew trust is a no-op on older Homebrew. Upgrade later with brew upgrade lazyide.
Scoop (Windows):
scoop bucket add lazyide https://github.com/TysonLabs/scoop-bucket
scoop install lazyideFrom source:
cargo install --git https://github.com/TysonLabs/lazyidePrebuilt binaries: GitHub Releases
Installer flags:
sh install.sh --with-deps # also install ripgrep + rust-analyzer
sh install.sh --prefix /usr/local/bin
sh install.sh --version v0.3.0After installing, run lazyide --setup to detect and install optional tools (rust-analyzer, ripgrep).
- LSP integration — rust-analyzer completions with inline ghost text, diagnostics, go-to-definition
- Syntax highlighting — Rust, Python, JS/TS, Go, PHP, CSS/SCSS, HTML/XML, Shell, JSON/TOML/YAML, Markdown
- Code folding — brace-based (Rust, JS, Go), indentation-based (Python), tag-based (HTML/XML)
- Bracket pair colorization —
{}()[]colored by nesting depth - Find & replace — regex search in file, ripgrep-powered project search. Jupyter notebooks (
.ipynb, any letter case) are searched by their cell text, so a hit jumps to the matching line of the cell view; outputs and metadata are not searched. A notebook that cannot be shown as cells is searched as raw JSON. At most 500 notebooks are searched per query; the status bar says how many were skipped. If ripgrep reports an error (for example an invalid regex), the status bar shows it - Word wrap — toggle with
Alt+Z, with full cursor/selection/mouse support - Horizontal scrolling —
Shift+scrollwhen word wrap is off
- Gutter markers — added
+, modified~, deleted-per line viagit diff - File status — tree colors files by status (modified, added, untracked) with directory propagation
- Branch display — branch name and change summary in the status bar
- Diff view — side-by-side working tree vs HEAD for the current file, hunk navigation, jump to hunk
- Change navigation —
Alt+N/Alt+Pjump to the next / previous changed region
- 32 themes — dark and light, with live preview browser
- Customizable keybindings — remap ~40 actions via config file or in-app editor
- Tabbed editing — dedicated tab row, preview tabs, sticky tabs, dirty indicators
- Split panes — two editor panes side by side or stacked, each with its own tab group; move tabs between them, drag the divider
- Minimap — condensed file overview at the editor's right edge with the viewport highlighted; click or drag to scroll,
Alt+Ito toggle - Status bar — focus mode, git branch with +/- counts, file breadcrumbs, cursor position, diagnostic counts; on a notebook tab, the cursor's cell (see Notebook commands)
- File tree — folders-first sorting, expand/collapse, context menus, resizable divider. A rename that switches an open tab between notebook view and plain text (adding
.ipynbto a file, or removing it from a notebook shown as cells) reopens that tab; if the tab has unsaved changes, the rename is refused ("Save x.py before renaming"). An.ipynbfile shown as raw JSON keeps its tab when renamed - Command palette —
Ctrl+Pfor quick access to all actions - Autosave & recovery — buffers saved every 2s, crash recovery on reopen
A Jupyter notebook (.ipynb, nbformat 4) opens as readable cell text in a normal tab, in the jupytext "percent" format:
# %% #k3x9qa
import pandas as pd
# %% [markdown] #p0d7mz
# # Title
# Some *markdown* text.
- Cells — each cell starts with a
# %%marker line (# %% [markdown],# %% [raw]). Markdown and raw lines carry a#prefix. Type a new# %%line to add a cell, delete a cell's lines to remove it. Text above the first marker becomes a code cell (it gets a marker line on save). - Cell tokens — the
#k3x9qaat the end of a marker is the cell's identity for this tab. It is not saved to the file. Keep it when you edit or move a cell, so the cell keeps its outputs. A cell without a token (or with a token from another notebook, or whose type changed) saves as a new cell. On save, new cells get a token on their marker line; each inserted token is one extra undo step, so undo after a save first removes the tokens, then your edits. - What is kept — saving writes back only the cells you changed. Every other byte stays as it was: cell ids, metadata, outputs, execution counts, number formatting and key order. A save with no edits does not rewrite the file. An edited cell keeps its (now stale) outputs, as in Jupyter.
- Edited cells are normalized — a changed cell's source is written with LF line endings, in the list form (unless the cell was a single string), with no trailing newline unless the last line is blank.
- Malformed markers — a line that starts with
# %%but is not a valid marker (for example a half-typed# %% #) makes the save fail with "Malformed cell marker at line N"; nothing is written. - Reformat prompt — if the file's JSON whitespace is not Jupyter's own layout (for example indent 2), the first save that changes it asks first. Cell contents and outputs are kept either way.
- Fallback — a file that is not a valid nbformat 4 notebook (or whose cell text contains a
# %%line) opens as raw JSON, with the reason in the status bar ("Opened x.ipynb as raw JSON: …"). - Changes on disk — changes are checked by the watcher and again immediately before a notebook save, so a save cannot race ahead of watcher debounce. A tab without unsaved edits reloads. With unsaved edits, a change of outputs, metadata or execution counts only is adopted without a prompt ("Notebook outputs updated on disk") and the next save keeps both. When the change is the result just written by Notebook: Run all, the watcher keeps the run's completion status instead. A notebook tab that was not active when its file changed (for example a run finished while you were in another tab or pane) is checked when you switch back to it. Any other change asks:
Rloads the disk version,K/D/Esckeep your version, and the next save overwrites the notebook on disk. - Autosave & recovery — recovery re-attaches cell outputs only if the notebook file is unchanged since the autosave; otherwise the text is recovered as new cells, and the prompt says outputs will not be re-attached. An autosave written in the other view (cell view vs raw JSON, for example one from before notebook support) is not offered; the status says so and the autosave file is kept (that tab does not autosave its own edits).
- Reopen as raw JSON — the palette commands "Reopen as raw JSON" and "Reopen as notebook" switch the current tab between the cell view and the plain JSON text (to edit metadata by hand). Save first; a tab with unsaved changes is not reopened. If an autosave was not recovered because it was written in the other view, reopening in that view offers it.
- Palette — the notebook entries ("Notebook: …" and "Reopen as raw JSON") are listed only when the current tab is a notebook, and "Reopen as notebook" only on an
.ipynbtab shown as raw JSON. "Notebook: Stop run" is also listed while a run is active. Other tabs show the usual palette.
These work on a notebook shown as cells. On any other tab the keys do what they did before.
| Command | Key | Palette entry |
|---|---|---|
| Next cell: the first line after the next marker | Alt+Down |
Notebook: Next cell |
| Previous cell: the top of the current cell, or of the cell above if already there | Alt+Up |
Notebook: Previous cell |
| Insert an empty code cell below the current cell | Alt+C |
Notebook: Insert cell below |
| Toggle the current cell between code and markdown (a raw cell becomes code) | — | Notebook: Toggle markdown/code |
| Clear all outputs | — | Notebook: Clear all outputs |
- Inserts and toggles are normal edits:
Ctrl+Zundoes them (a toggle takes two undo steps). An inserted cell gets its token on save, like any new cell. - A toggled cell changes type, so it saves as a new cell and a code cell's outputs are dropped. A toggle is refused when a line would become a cell marker (for example the
%%timemagic). - Clear all outputs marks the tab dirty. On the next save every code cell gets
outputs: []andexecution_count: null; markdown and raw cells are unchanged. Reload the file to cancel.Ctrl+Zdoes not cancel it. - Status bar: the breadcrumbs end with the cursor's cell, numbered as on disk:
analysis.ipynb › cell 4/12 · [7] · text, image/png(execution count and output types),cell 5/12 · [8] · error: KeyError,cell 6/12 · edited, outputs stale(the cell changed since it ran), ornew cell(not saved yet, or its type changed). A status message shows first, as for any tab. - macOS Terminal:
Alt+Up,Alt+DownandAlt+Cneed "Use Option as Meta key" (Settings > Profiles > Keyboard). Without it, use the palette entries. The keys can be remapped asnext_cell,prev_cellandinsert_cell_below.
- Notebook: Run all (nbconvert) (command palette only) runs the whole notebook in the current tab with
jupyter nbconvert --to notebook --execute --allow-errors, in the notebook's directory, with the notebook's own kernel. It needsjupyter nbconvertandipykernel(pip install nbconvert ipykernel). There is no single-cell run and no live kernel. - Save first: a tab with unsaved changes is refused ("Save before running"). One run per notebook at a time; the status bar shows "Running x.ipynb…" while it runs.
--allow-errorskeeps going after a failing cell and writes its traceback, so the status reports the first failing cell ("Run finished: cell 5 raised KeyError") or "Run finished in 12s". A run that fails to start (for example a missing kernel) shows the error and leaves the notebook unchanged.- The result replaces the notebook on disk, and the open tab picks up the new outputs. If the notebook changed on disk during the run, it is not overwritten: the result is saved as
x.executed.ipynbnext to it (orx.executed-2.ipynb,-3, ... if that file exists; an earlier result is never overwritten). - Notebook: Stop run kills the run of the current notebook (or the only active run) and discards its partial result. With several runs active, open the notebook to stop first. A run continues if its tab is closed, and its result is still applied and reported. Quitting lazyide stops active runs.
Global
| Key | Action |
|---|---|
Ctrl+P |
Command palette |
Ctrl+O |
Quick open (fuzzy search) |
Ctrl+S |
Save |
Ctrl+W |
Close tab |
Ctrl+Q |
Quit (press twice if unsaved) |
Ctrl+B |
Toggle file tree |
Ctrl+F |
Find in file |
Ctrl+H |
Find and replace |
Ctrl+Shift+F / Alt+F |
Search project (ripgrep) |
F5 |
Git diff of current file |
Alt+V / Alt+S |
Split editor vertically / horizontally |
Alt+W |
Focus other pane |
Alt+M |
Move tab to other pane |
Alt+X |
Close pane |
Alt+I |
Toggle minimap |
Ctrl+N |
New file |
Ctrl+R |
Refresh tree |
Alt+Z |
Toggle word wrap |
F1 / F2 |
Previous / next tab |
F4 |
Help |
Tab / Shift+Tab |
Focus tree / editor |
Editor
| Key | Action |
|---|---|
Ctrl+Space |
LSP completion |
Ctrl+D |
Go to definition |
Ctrl+G |
Go to line |
Ctrl+J |
Toggle fold |
Ctrl+U |
Toggle fold all |
Ctrl+Z / Ctrl+Y |
Undo / redo |
Ctrl+/ |
Toggle comment |
Ctrl+C / Ctrl+X / Ctrl+V |
Copy / cut / paste |
Ctrl+A |
Select all |
Shift+Alt+Down / Up |
Duplicate line |
F3 / Shift+F3 |
Find next / previous |
Alt+N / Alt+P |
Next / previous git change |
Alt+Down / Alt+Up |
Next / previous notebook cell (notebook tabs only) |
Alt+C |
Insert notebook cell below (notebook tabs only) |
PageUp / PageDown |
Scroll page |
Ctrl+Home / Ctrl+End |
Start / end of file |
Tree
| Key | Action |
|---|---|
Up / Down / K / J |
Navigate |
Right / L / Enter |
Open / expand |
Left / H |
Collapse / parent |
Delete |
Delete (with confirmation) |
Mouse
- Click file/folder in tree to open
- Drag divider to resize panes
- Right-click tree for context menu (New File, New Folder, Rename, Move to, Delete)
- Click + drag in editor to select text
- Right-click editor for edit menu
- Click gutter fold icons to toggle folds
- Shift+scroll to pan horizontally
Custom keybindings
Override any shortcut in ~/.config/lazyide/keybinds.json:
{
"save": "ctrl+s",
"redo": ["ctrl+shift+z", "ctrl+y"],
"fold_toggle": "ctrl+j"
}Or use the in-app keybind editor: Ctrl+P > "Keybind Editor"
cargo build --release
cargo run -- /path/to/project
cargo testRequires Rust 2024 edition.
| Tool | Purpose | Install |
|---|---|---|
| rust-analyzer | LSP completions, diagnostics, go-to-definition | lazyide --setup |
| ripgrep | Project-wide search | lazyide --setup |
| git | Branch display, gutter markers | System package manager |
| jupyter nbconvert + ipykernel | Run notebook | pip install nbconvert ipykernel |
- Architecture — module structure, data flow, rendering pipeline
- Notebook design — how
.ipynbfiles are projected, saved and kept safe - Demos — recorded demos (GIF/MP4) and how to re-record them
- Contributing — themes, bug reports, code contributions
- lazygit and lazyssh — inspiration for the terminal-native approach
- ratatui — TUI framework
- rust-analyzer — LSP server
- ripgrep — project search engine


