mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 08:02:24 +00:00
791d0d0cfa5bec7b99cf9e87b16e86ce9f18f4ec
30
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
791d0d0cfa |
Merge remote-tracking branch 'origin/main' into polish/ralph-wc
# Conflicts: # README.md # README.zh-CN.md # crates/tty7-cli/src/cli.rs # crates/tty7-cli/src/server.rs # crates/tty7-core/src/core/config.rs # crates/tty7-core/src/core/git/status.rs # crates/tty7-core/src/daemon/install/wsl.rs # crates/tty7-core/src/daemon/protocol.rs # crates/tty7-core/src/daemon/spawn.rs # crates/tty7-core/src/daemon/ssh/mod.rs # src/terminal/completion.rs # src/terminal/remote.rs # src/ui/app.rs # src/ui/i18n/en.rs # src/ui/i18n/ja.rs # src/ui/i18n/zh.rs # src/ui/tree_sync.rs |
||
|
|
958d8b7442 |
feat(window): dock the code panel and the diff overlay beside the terminal (#625) (#685)
* feat(window): dock the code panel and the diff overlay beside the terminal (#625) Opening a file covered the workspace. The terminal underneath kept running and was neither visible nor typeable, so reading a file while an agent talked was a toggle loop: open it, close it to read the reply, open it again. The Files tree already docks; the two surfaces you go to *from* it did not. They dock now, as a flex sibling of the terminal column rather than a narrower overlay — that distinction is the feature. `set_grid_size` is driven by the terminal element's laid-out bounds, so a column takes width away from the grid and the PTY reflows into what is left; a card painted over half the workspace would have left the grid full width with half of it hidden. `overlay_top` stops ordering a pair and starts choosing between them: a column has one child, and two `flex_1` siblings would split it and fight. Fill mode keeps the old vector, the old opaque paint and the old platform hoist untouched, so nothing about today's overlay changes for anyone who picks it. - Half the terminal column by default; drag the divider, double-click it to cycle a third / half / two thirds, or use the palette commands. Two thirds deliberately runs past the half-window cap the side panels obey — only the terminal's floor binds it. - `DOCUMENT_MIN_W` joins the width budget: both side panels reserve it the way they already reserve each other, and the column is derived from the *live* sidebar and panel widths rather than their floors, so a panel someone dragged wider is width the terminal keeps. - A window too narrow to seat both fills for that frame. The fallback is derived at render time and never stored, so widening re-docks on the next frame with nothing to undo. - Fill or dock is per tab, on the header's context menu. Reading a long file over the whole window in one tab while an agent keeps half of another is the normal case, and one global switch made each of those flip the other. A tab that has not been told reads `document_layout` from the config, which is what a fresh tab starts as — and which the menu therefore does not write, since every untold tab is reading it. - Everywhere but macOS the title bar spans the workspace, which left a bar's height of nothing above the column. The header is drawn into it, and behaves like the title bar it now sits in. With the detail panel closed the column reaches the window's right edge, so the header stops short of the trailing chrome through a width the tab strip's own reservation shares. - The docked headers drop the traffic-light inset they never had to clear, and the diff header's branch name becomes the thing that yields so the view toggle and the close tile survive a column's width. New in `config.json`: `document_ratio`, and `document_layout` for what a fresh tab starts as. Four new actions, bindable and unbound by default. * fix(window): hold the docked column to widths the strip and the file agree on Three defects in the document column, each with a guard test that fails without its fix. The tab strip did not know a column had taken width off it. On macOS the strip lives inside the terminal column and sizes itself to the window less the detail panel, so a docked document left it 340 points wider than the column it sits in and the chips ran on under the column — the same overrun the panel's own reservation was added for. Everywhere else the strip spans the workspace and the column's hoisted header is drawn over its trailing end with no fill of its own, so a chip left under it showed through the file name and stayed clickable through it. The column's width now comes off `strip_w` on macOS and off `corner_w` elsewhere, which is where the panel's already goes. The divider wrote widths the file would not keep. `Config::sanitize` holds `document_ratio` to 0.2..=0.8; the drag clamped in pixels only, so a column pushed against either edge of a wide window was saved outside that band and reopened somewhere else — on a 2560-point body, 232 points from where it was dropped. The band is a pair of shared constants now and the drag clamps to it, the way the font size and its stepper were made to agree in #550. The palette named the config's layout rather than the tab's. Fill is per tab, so a tab told to fill was still offered "Document: Fill Window" — a row that named the state it was already in and did the opposite. It reads the active tab through `ChromeState` now. Also: `document_layout`'s doc comment still described the global switch an earlier draft had, three lines after the field became a per-tab default. |
||
|
|
0295a98915 |
feat(sftp): open remote text files in the built-in editor
A click on a file in the SSH Files panel used to start a download; the only way to change a remote file was download, edit, re-upload. Now a click opens it in the built-in editor and Cmd-S saves straight back over the pane's own SFTP channel, matching what the Files panel already does locally and over a remote workspace. - protocol: SftpOp::ReadFile/WriteFile and SftpOpResult::File, bytes as base64; the reply carries the body plus the stat it was read under - daemon: ReadFile enforces the caller's size ceiling before and during the read; WriteFile rewrites in place (truncate, not temp-and-rename) so the file keeps its mode and ownership - SftpHost: a Host over the pane's SFTP route, so the editor's existing open/save path works unchanged; git/search/watch honestly Unsupported - editor: an open buffer holds the host it was read from, and save/reload/dedup/watch key on (host, path) instead of the active host - panel: single click opens (dirs navigate, text files edit), the same gesture as the local tree; binary or oversized files get the local tree's toast, and Download moves to the context menu Review follow-ups, in this PR: the SFTP host stays out of HostRegistry, which means "a machine this window has a link to" and is swept as such — filing the pane's channel there made Cmd-S return silently once a workspace deletion took it back out. The cursor-jump lookup, the status bar's path, and the SCM panel's repository all key on the buffer's own host now. Closes #656. |
||
|
|
005efee058 |
fix(ui): explain the io errors that build their own message
`explain_io` exists because "Permission denied (os error 13)" answers a developer's question and not the reader's — its own doc says so. Every failure routed through `HostOps::notify_err` gets it. Four that build their notification string by hand did not, and printed the raw error. The clearest symptom was inside one file: saving a file in the editor went through `notify_err` and explained itself, while opening the same file, denied for the same reason, said `os error 13`. Both file-link openers are changed together — the file tree's opener deliberately shares its wording with the terminal's (#542), so explaining one and not the other would have split a pairing that was on purpose. The `log::warn!` next to each call keeps the exact error. A developer reading a log and a person who just lost a save want different things, so both are written rather than one chosen — noted on `explain_io` along with the rule, since it was the absence of a stated rule that let four callers drift. |
||
|
|
d343fd8a13 |
feat(links): open file links in tty7, resolved on the pane's own host (#568)
* feat(links): open file links in tty7, resolved on the pane's own host A clicked file path now opens in the built-in editor at the line and column the link named, and the Files panel reveals it; a directory link opens the panel on that directory. Settings -> Terminal -> Links -> Open files with picks between the built-in editor, the OS file association and a command, migrating anyone who had already set link_file_command. Detection is split into a filesystem-free candidate parser and a probe callback, so a pane whose paths live on another machine resolves them there instead of against the local filesystem -- an absolute path used to open this machine's copy silently. A pane running ssh typed into a local shell can answer for neither side and no longer offers file links at all. Relative paths are measured from the directory the work is happening in (the agent's, not the shell's kernel cwd) and then from the repository around it, and a path that matches nothing under either now says so instead of the click doing nothing. * fix(links): keep a remote path off the local openers, and off a dead end Review follow-ups on the file-link work. - A file resolved on another machine now opens in the built-in editor whatever `link_file_open` says. Under `system` or `command` the path was handed to a local `open` / `code --goto`, which threw away the resolution just done on the pane's host and silently showed this machine's copy — the same bug this branch set out to fix, left live for two of the three modes. A directory outside every tree root says so instead of opening a local file manager on a path that belongs to the far side. - `flush_link_probes` takes the host before it takes the wanted paths. `take_wanted` moves them into the in-flight set on the promise that a call is carrying them; a host that had gone away broke that promise for good and left those paths permanently unanswered — no underline, and a click that says nothing. - `~` no longer borrows this machine's `$HOME` for a pane whose paths are elsewhere. A cwd outside `/home` and `/Users` used to fall back to it, so `~/.zshrc` on a Linux box became `/Users/me/.zshrc` and was asked about — and possibly answered — over there. - An unresolved absolute or `~`-rooted path no longer claims it was looked for under the pane's directory. It never was: roots are only for relative paths. - A pending tree reveal counts down whether or not its row was found. A row that never reported bounds kept the request alive for good, re-issuing a scroll on every render and holding the column against a hand scroll. - The repo root comes from `GitStatusCache` when the git-status probe has already asked about that directory, rather than a second round trip. Tests: the migration `link_file_open` exists for (an old config with a command lands on Command, one without on the editor), a probe with no host staying wanted, and `~` refusing this machine's home for another one. * test(links): only claim a leading slash is absolute where it is `is_rooted` asks `Path::is_absolute`, the same question `FileCandidate::paths` asks before it decides the roots do not apply — and on Windows `/etc/hosts` answers no to both. The predicate is consistent; the assertion was not, so it now lives in a unix-gated test of its own next to the untouched one. --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
2dc6a88af6 |
merge: main into the Source Control branch
Conflicts were the two streams touching the same seams, resolved by taking the newer decision on each side: - main's interface font scale (rems tokens) wins in right_panel.rs; the SCM panel keeps its local px steps until it moves onto that scale, and the now-unused PANEL_TEXT constants are gone. - main's l10n_keys! macro (idents only) means the key list carries no doc comments any more; our SCM keys fold into it, and PanelUntracked stays deleted — its only caller was the panel this branch replaced. - main's Command::localized palette style carries our Git group; ORDER keeps main's visibility and our width. - main's ansi_seed/clear_ink refactor in presets.rs carries the lane colours: lanes() now clears through the same helper semantics uses. - file_tree keeps both: main's drag-and-drop targets and this branch's git decorations per row. - diff_overlay keeps both: main's sidebar-count write-back on snapshot install and this branch's epoch read and untracked preview. - main's window.prompt SSH-close confirmation supersedes the bespoke modal our branch still carried; main's tile-glyph revert stands. - main's two new guards are satisfied: the fourteen SCM actions carry authored names on the Keybindings page (their palette wording, plus a new CmdGitToggleGraph), ja translates ScmDetached, and CmdGroupGit joins the kept-in-English list — Git is a name. 2571 tests, 0 failures. |
||
|
|
62b922f2c2 |
Merge origin/main into integration/polish
main dropped the client-side command-mark store (#404) while this branch had just started reading it: the close confirmation names the command it is about to end, and the mark was the only place that text existed on the client. Keep both. The OSC 133 tokenizer main left in place already sees every mark, so the command line now rides alongside `zle_reading` and `shell_vi_mode` as one shared string — set on `C`, cleared on `B` and on a `C` that carries no line — instead of a store with a list, a lock and a cap. `busy()` reads that. The rest: - settings.rs takes main's opaque overlay surface and background layers, keeping this branch's no-match note and scrolled body. The inner `.bg()` goes, per main's reason: the root already paints it, and a second fill hides the theme image. - i18n keeps this branch's `every_key_is_translated_in_every_locale`, which walks `L10nKey::ALL` in all three locales, over main's hand-listed zh coverage test it replaced. It immediately caught three of main's new backdrop keys reading English in ja — Mica, Mica Alt and Acrylic, which is what Japanese Windows calls them, so they join the allowlist with that reason. - app.rs keeps both sides' tests and drops both sides' now-dead imports: `window_background` (main deleted the function) and `humanize_action` (this branch's keybinding note uses `keymap::action_entry` instead). Verified: `sleep 300` then ⌘W asks about "sleep 300"; ⌘W after it ends closes without asking. |
||
|
|
0106430ecd |
merge: main into the Source Control branch
The one conflict is an import list in `diff_overlay.rs`: this branch added `SharedString` for the unified view's row labels, main added `Background` and `Hsla` for the window backdrop work. Both sides are still used, so the resolution is the union. Worth recording why this merge happened when it did. `main` moving is not normally urgent — branch protection dropped its strict check, so a branch behind main still merges — but a *conflicting* branch is different: GitHub cannot compute `refs/pull/N/merge`, and every workflow that triggers on `pull_request` silently stops running. Three pushes in a row registered no CI at all on #424 while other PRs kept going green, which reads as a GitHub incident and is really just an unresolved conflict. |
||
|
|
61efe27f2d |
feat(windows): add native backdrop material presets (Mica / Acrylic /… (#412)
* feat(windows): add native backdrop material presets (Mica / Acrylic / Blur) Adds a Background material dropdown (Auto / Blur / Mica / Mica Alt / Acrylic / Off) that maps onto the native Windows backdrop APIs already provided by the gpui fork — Mica and Mica Alt via DwmSetWindowAttribute(DWMWA_SYSTEMBACKDROP_TYPE), Acrylic via the new DWMSBT_TRANSIENTWINDOW material, and Blur via the classic ACCENT_ENABLE_ACRYLICBLURBEHIND path — with no fork changes required. * config: introduce WindowBackdrop in tty7-core with lenient kebab-case deserialization, defaulting to Auto for existing configs * theme: resolve the backdrop through a build-number fallback chain (Mica/Mica Alt need Windows 11 22H2, Acrylic needs 22H2 natively and 1809 via classic acrylic, Blur needs 1809; older builds fall back to plain translucency) and default the background alpha to SYSTEM_MATERIAL_OPACITY (0.82) while a material is active * settings: replace the blur toggle with a localized backdrop dropdown that only lists the presets the current Windows build actually supports, and keep the settings panel fully opaque so workspace translucency never shows through it * theme: make the file sidebar and right detail panel follow the window opacity so the backdrop material shows through the whole workspace, keeping row-level accents opaque for readability * i18n: add backdrop keys for en, zh-CN and ja-JP, covered by the translation completeness test * feat(theme): let the sidebar and right panel follow the window opacity * update GPUI * fix(windows): gate the sidebar translucency to translucent windows and sync the opacity slider fix(windows): gate the sidebar translucency compensation to active materials * fix(windows): derive the material opacity default from the resolved appearance * fix(theme): keep WindowBackdrop semantics consistent on non-Windows f * fix(theme): stop Windows-only materials from pinning the blur on other platforms * docs(changelog): document the Windows backdrop material settings * refactor(theme): share the default window-opacity derivation * fix(ui): keep gradient presets behind the settings panel and scope its fallbacks * fix(ui): keep the settings theme picker legible and the backdrop label honest f * fix(theme): let every backdrop variant defer to the local blur toggle on non-Windows * fix(settings): restore the backdrop dropdown selection on locale refresh * fix(ui): keep the opened-file editor surface opaque under window translucency * fix(settings): rebuild backdrop options after selection * fix(settings): ignore synced windows backdrop overrides on other platforms * fix(settings): preserve synced windows backdrop on non-windows reset * fix(diff): keep the full-window overlay background opaque * fix(windows): keep Auto opaque and stop the backdrop from misreporting itself Ten findings from a review of the backdrop-material work, all in the Windows-only paths. The root one: `material_active` treated `Auto` as a material whenever the legacy blur toggle happened to be on. `Auto` is the default in every config written before this setting existed, and plenty of them carry `window_blur: true` from the switch that no longer renders on Windows, so an untouched install would drop from opaque to 0.82 alpha - with its file sidebar and right panel at 0.15 - on first launch after the update, with no visible control to undo it. Only an explicit pick in the dropdown now buys the translucent defaults. The switch comes back on Windows while the backdrop is `Auto`, since that is exactly when the legacy flag still decides something. The rest: - Mica and Mica Alt fell back to `Blurred` with no lower bound, asking for a blur that does not exist below 1809 - and build 0, which is what a failed `RtlGetVersion` reports. They now degrade to plain translucency like `Blur` and `Acrylic` already did. - Acrylic is no longer offered below 22H2, where it resolves to the very same classic WCA blur as `Blur`. A test now asserts that no two offered presets render identically on any build. - `reload_from_config` re-applied the theme and the opacity slider but not the backdrop dropdown, so an external config change switched the window's material while the control kept naming the old one. - The settings, opened-file and diff overlays were made opaque so the OS backdrop cannot show through their text; that also hid the theme background image, which used to show through them. They paint their own copy of it now, and the fill they share moved into `theme::overlay_background`. - The SFTP transfers tray painted `workspace_surface_color` inside the right panel, which already paints it, stacking the same translucent surface twice into a darker band with a hard seam. - `apply_theme` re-issued `set_background_appearance` on every `Config` mutation in every window. With a DWM material that now costs a `SetWindowPos(SWP_FRAMECHANGED)` frame recalc, so dragging the opacity slider recalculated the frame once per mouse sample; it is skipped when the appearance is unchanged. * fix(ui): dim the overlay background image, and stop telling Windows it is macOS Two defects found while driving the previous commit's changes in the app. The overlays repaint the theme background image over their own opaque fill, so it survives them being made opaque - but nothing dimmed it. Before those overlays were opaque the image reached the eye through their translucent fill; painting it at full strength put the settings text straight on top of the wallpaper and made the panel unreadable at any image opacity above about half. They now paint the image and then the workspace's own fill over it, which is exactly the strength the image had through these overlays before, and which needs no new constant to say so. Shared as `app::overlay_surface_layers`, empty when the theme has no image so a themeless window paints no second pass of anything. The Windows-only blur row reused `SettingsBlurDesc`, whose text ends in "(macOS)". It gets its own key in all three locales, describing the job the flag actually still has on Windows: feeding the `Auto` material. --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
7695287f4a |
feat(git): connect the remaining invalidation sources and subscribers
The watcher landed with one subscriber declared and one invalidation source wired, because the three others live in files it did not own. Wiring them: - the file tree announces working-tree edits it sees, skipping anything under .git so the repository's own watch is not doubled into the same window - the editor announces a save, which is the working-tree edit neither watch can see when the tree is not showing that directory - a pane announces a command boundary, which is the only signal for a command that edits a file nowhere anyone is looking. It only moves the epoch: the probe rides the app's next render, which refresh_git_status is about to cause anyway by writing GitStatusCache The tree and the editor also declare themselves as watchers, so decorations and gutters keep a repository live on their own rather than only while the panel happens to be the visible tab. Both target the active pane's repository, which is what the panel picks too, so the three subscriptions usually collapse onto one watch. |
||
|
|
6b8194bd67 |
fix(editor): show the file tree the empty editor tells you to use
Opening the code panel with no file draws "Open a file from the file tree" and hands the tree keyboard focus — but nothing put the tree on screen. On a fresh tab ⌘⇧E gave you an empty editor across the whole content area, naming a panel you could neither see nor reach from there; ⌘J did not help, because the code panel had the space. The branch that reaches for the tree now reveals it first, using the file_tree_on_screen check that already existed. Verified end to end: ⌘⇧E opens the editor with the tree beside it, and clicking a file in it loads the file. |
||
|
|
d1f61e109f |
fix(editor): give the Markdown preview a scrollbar
The editor itself gets one from `Input`; the rendered-Markdown pane beside it scrolled a whole README with nothing to say how far down it was. It now carries the same shared bar as the sidebar, the file tree, the right panel and the settings pages, on a handle kept per open file — so switching away and back lands where you were reading. `with_vertical_scrollbar` grows by `flex_1`, which needs a column with a height of its own around it; dropped straight into the overlay it sizes to its content and the pane stops scrolling altogether. That requirement is now written on the helper, where the next caller will read it. |
||
|
|
b77f70310d |
fix(prompts): put the action on the right and give Escape a home
Every confirmation dialog in the app was built as `&[Cancel, Delete]`, and gpui hands answer 0 to the platform first — which NSAlert draws on the *right* and gives Return. So the app shipped 14 dialogs with the buttons mirrored: Delete sat on the left, exactly where a decade of macOS has trained people to expect Cancel, and Cancel sat on the right holding the default. Escape was worse: it did nothing at all, anywhere. gpui only sets the Escape key equivalent on a `PromptButton::cancel`, and every call site passed plain strings, which become `PromptButton::Other`. There was no way to dismiss any of these dialogs from the keyboard except by pressing Return. A shared `ui::confirm_answers(action, keep)` now builds the pair, so the arrangement is decided in one place: | | Before | After | |---|---|---| | Right button (Return) | Cancel | the action | | Left button | the action | Cancel | | Escape | nothing | Cancel | | Space / initial focus | Cancel | Cancel | | `Ok(1)` means | act | — | | `Ok(0)` means | cancel | act | Verified on the file-tree delete, end to end: Escape leaves both files in place, Return removes only the one that was right-clicked. Also checked on the ⌘W busy-pane guard, where Escape and Space keep the tab and Return closes it. Two prompts keep their own shape and say why in a comment: - The daemon-mismatch prompt at launch offers Quit and Restart Server and nothing else. With no answer that leaves things alone there is nothing safe to give Escape, so Quit stays on Return — it loses no sessions, while restarting the server ends every one of them. - The unsaved-editor prompt has three answers. Save keeps answer 0, Cancel is marked so it takes Escape, and Discard sits on the far left where nothing lands by reflex. gpui puts the initial keyboard focus on Discard so it stays reachable without a mouse. |
||
|
|
befb689a1a |
fix(editor): keep Discard away from the button Return presses
The unsaved-changes sheet listed [Save, Discard, Cancel]. The platform draws the first entry as the default and lays the rest out beside it, so that rendered as "Cancel | Discard | Save" — with Discard directly adjacent to the key Return lands on. Apple puts Cancel between them for exactly this reason. Reordered to [Save, Cancel, Discard], which renders "Discard | Cancel | Save", and moved the discard arm to index 2 to match. Tested: the index mapping reads 0 = Save, 1 = Cancel (falls through to no-op), 2 = Discard. The right-to-left rendering was confirmed on the two-button prompts, where array index 0 is drawn rightmost and takes Return; the three-button sheet itself was not driven on screen. |
||
|
|
63ad270473 |
fix(errors): give the failures people can act on a sentence
Every host operation that fails renders as "{context}: {raw io::Error}".
Display on an io::Error answers "what happened" for someone reading a
log; it does not answer "what now" for the person who just lost a save,
and "Permission denied (os error 13)" is the shape of that gap.
The six kinds that change what you would do next — denied, gone, no
space, read-only, busy, timed out — now read as one authored sentence in
all three locales. Everything else keeps its raw detail rather than
losing it to a vague house message.
Also: "Save failed" did not say which file, and with more than one
editor tab open that is the first thing you need to know. It is now
"Could not save {name}".
|
||
|
|
8a342f2ca9 |
feat(ui): GUI localization for en and zh-Hans (#303)
* feat(ui): add GUI localization for en and zh-Hans
* feat(ui): localize search placeholders and relative time
* feat(ui): localize palette, switcher, and sftp strings
* feat(ui): localize home shortcut labels
* feat(ui): localize tray, ssh prompt, and editor strings
* feat(ui): add plural/select i18n helpers and localize sftp/settings labels
* feat(ui): localize settings search, forwards panel, and file tree
* feat(ui): localize code editor and right panel
* feat(ui): localize stop/delete workspace confirmations with plural support
* feat(ui): localize diff overlay with plural-aware summary
* feat(ui): localize pending pane, worktree prompt, and home time strings
* feat(ui): localize app menus, tray, tab strip/sidebar, and remote status strings
* feat(ui): localize switcher, file_tree, machine_mirror fallback strings
* feat(ui): localize ssh prompts, theme presets, host error wrapper, and finish remote strings
* feat(ui): localize command palette strings
* feat(ui): localize app.rs notifications, prompts, placeholders, and parse errors
* feat(ui): localize remaining theme, switcher, settings, and sftp strings
* style: cargo fmt
* feat(ui): add language selector to settings
* fix(ui): refresh locales across windows
* refactor(ui): make GUI language selection explicit
* fix(ui): localize Explorer settings after merge
* fix(ui): keep persisted theme names out of the GUI locale
A theme's name is data, not chrome: it is written into the theme YAML and
matched back with `trim_end_matches(" (custom)")`. Translating it meant a
Chinese GUI forked "Nord" into "Nord(自定义)", the next fork stacked a second
suffix on it, and the name stayed Chinese after switching back to English. The
derived-name fallback had the same problem. Both are English again.
Also in this pass:
- Give each test thread its own locale override. The locale is process-wide and
tests run in parallel, so the two tests that switched to zh-CN could flip the
language out from under another thread's English assertions.
- Rebuild the menu bar when gui_language changes in config.json, the way the
in-app picker already does — otherwise the menus kept the old language.
- Document the values the setting actually accepts. The docs still described
`auto` and `zh-Hans`, which sanitize() resets to `en`.
- Put the English words back into the Chinese search keywords for the language
setting; the other 58 keyword sets keep them.
- Drop the unused is_zh_hans helper.
---------
Co-authored-by: thomas <thomas@gmail.com>
Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
|
||
|
|
8c1946d763 |
chore: strip every comment from the Rust sources (#268)
Removed all Rust comments -- line, block, and doc -- from the 139 tracked .rs files with `uncomment` 3.5.1. It parses each file with tree-sitter instead of matching text, so comment-like content inside string literals is left alone: the JavaScript plugin source embedded in agent_hooks.rs raw strings keeps its own `//` lines. Left alone: Cargo.toml comments and the shell scripts under scripts/. Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
bed22d899e |
Keep workspaces whole: remote reopen/restart recovery, and cross-workspace restore guards (#257)
* feat(remote): keep a remote workspace whole across reopens and restarts Reopening a remote workspace — or coming back to one whose `tty7-server` had been replaced — landed on a screen of `tty7 — disconnected` panes with their coding-agent conversations gone. Several independent holes added up to that; this closes them together, and picks up the surrounding work the same session produced. **Telling a restarted server from a blinked link.** `ControlHelloOk` now carries an `instance` minted once per server *process*. Nothing else in the handshake changes across a restart — `build` and both dialect numbers survive it — so a reconnect had no way to know its `pane_id`s were dead. It does now: a different instance rebuilds the window from its layout (same tabs and splits, fresh shells in the saved cwds) instead of re-attaching to a process that is gone. An absent instance means *unknown* and is never read as a restart. **An attach can now fail.** `Attach` has no synchronous reply, so the client returned `Ok` unconditionally and the daemon's `Error` frame was read much later by the reader thread, which has no arm for it — the pane then landed in the *link is down* state instead of falling back to a fresh shell. The client now reads far enough into the reply to classify it on the kind byte (the snapshot behind it can be megabytes) and hands those bytes to the reader thread, so a successful attach loses none of its replay. Local and remote attaches get different waits: the local one is on the UI thread. **The agent session survives to be resumed.** `TerminalView` raises `AgentSessionChanged` when the pane's agent reports a new native session id, so the layout on file catches up instead of waiting for the user to happen to open a tab. A pane that is still connecting now carries its agent through `PendingSpawn` — a save landing in that window used to write `agent: null` over the record — and `land_pane` sends `--resume` when the attach turned out to need a fresh shell. **Ending sessions says so on file.** "End Sessions" kills the panes and then drops their ids from the record, pushing the cleared layout to the machine that owns it (design §10: the remote's copy wins, so a local-only clear would be undone by the next open — the open this exists for). **The new-tab dropdown lists the window's machine.** `Host::shells` and a `Shells` control request (dialect v2) make the "+" menu a property of the machine the window is bound to. A remote window filled from this computer's `/etc/shells` offered `/bin/zsh` on a box whose zsh is elsewhere, and every pick failed to spawn. **An install reports its bytes.** The download and the SFTP upload each report progress, relayed to the client over the routed connection as a `RoutePrompt::InstallProgress`, and painted as a bar under the machine's row in the switcher. ~8 MB across two hops behind the word "connecting…" was indistinguishable from a hang. **The installer compares dialects, not version strings.** `tty7-server --protocol` prints what a binary speaks without starting it, so a connect adopts an already-running server it can talk to rather than prompting about a build difference and uploading 8 MB the machine did not need. **Switcher.** A machine's `⋯` menu holds "New Workspace" (it was a row under every machine, pushing the list a quarter of a card down) and a new "Disconnect", which drops the connection and leaves the windows open and read-only. The suspension lasts exactly as long as that machine has a window on it. Also drops three design/contract docs for the now-shipped remote-workspace work. * fix(session): stop one workspace's panes from being restored into another A restart put a copy of one workspace's seven tabs — cwds, layout and recorded agent sessions — in front of another workspace's own tabs, and auto-resumed every one of those agents a second time: six `claude --resume <id>` pairs running in parallel against the same conversations, one set per window. The record-level corruption that seeded it is still unattributed, but every mechanism that let it propagate, amplify, or go unnoticed is closable, and this closes them. **Panes now know their owner.** `Spawn` can carry the workspace the pane is created for; the daemon stores it immutably and reports it in `List`'s `PaneInfo.owner`. Restore refuses to re-attach a pane another workspace owns (`pane_attachable`) — before this, a saved id landing on somebody else's live pane attached silently, which is how one window could pick up another's shells. The field rides a new `SPAWN_OWNED` frame with a struct payload (the legacy spawn payloads are positional tuples an old daemon cannot grow), gated on a new `pane-owner` feature string: a client only sends it to a daemon that advertises it, so the legacy kinds stay byte-for-byte what old daemons expect. A pane with no recorded owner stays attachable by anyone — that is the pre-field behavior, not a new risk. **Saved pane ids are bound to the daemon process that issued them.** `DaemonVersion` now carries an `instance` minted once per process (the local twin of the control hello's), the GUI caches it at the `ensure_running` handshake, and each local workspace records it as `daemon_instance` beside its layout. Claiming a workspace whose ids came from a different instance blanks them first: daemon pane ids restart from 1, so after a reboot every saved id points at whatever unrelated shell holds the number now, and the aliveness check cannot tell a survivor from a squatter. A blank on either side means "cannot tell" and never trips it. Unlike the duplicate-claim case below, this path keeps the agent resume — the pane is genuinely gone with its daemon, and the fresh shell resuming the conversation is the feature. **A duplicate claim loses its agent resume along with its pane id.** `dedupe_pane_ids` kept the loser's layout *and* its `agent_session_id`, so the blanked leaves took restore's spawn-fresh path and auto-typed `claude --resume` for conversations the winning workspace's panes were still running — the doubling above. The winner keeps the panes and the resume; the loser keeps only cwds. **Cross-workspace saves are caught at the write.** Every terminal view remembers the workspace whose window created it, and `save_session` logs an error naming both ids if a window ever records a pane created for a different workspace — the tripwire for the still-unattributed seed corruption, so a recurrence is caught in the act instead of reconstructed from `session.json` archaeology days later. Wire compatibility both ways: `PaneInfo.owner`, `DaemonVersion.instance` and `Workspace.daemon_instance` are `#[serde(default)]` struct fields (old peers' JSON decodes, new fields are ignored by old readers), and `SPAWN_OWNED` is feature-gated as above. `daemon_instance` is client-owned in the design-§10 storage split — it names the local daemon, and the field-census test pins the classification. * fix(session): resume the agent when a local pane dies mid-restore `session_to_pane` decided whether to send a coding agent's `--resume` from `restore.is_none()` — i.e. from whether the pane looked alive when the restore started. But `alive_panes_on` runs one `List` at the top of the restore, while the attaches happen per leaf afterwards. A pane that exited in between failed its attach, fell back to a fresh shell inside `spawn_shell_terminal_in`, and then landed in the `restore.is_some()` arm: an empty shell with its conversation dropped. `ShellParts.restored` already answers this exactly, and the remote path already reads it in `land_pane`. Carry it onto `TerminalView` so the synchronous local path can read it too, and branch on that instead of re-deriving the answer from a set that may be stale by the time it is used. No behaviour change on the paths that were already correct: a view that was never restoring anything reports `restored: false`, which is the same answer `restore.is_none()` gave them. * fix(remote): check the server instance against the record, not just memory A remote workspace's pane ids were only guarded against server restarts by `RemoteLinks::instances`, an in-memory map. On the first connect after the client starts, every machine is a first sighting, so `server_restarted` answers false — and a `tty7-server` that was replaced while the client was closed sails straight through. Its pane ids restart from 1, so the saved ones now name unrelated shells, and the reconnect attaches to them: the exact id-reuse failure the local side already guards against. `Workspace::daemon_instance` was local-only for the stated reason that a remote server's identity is tracked live per connection. That tracking is correct but not sufficient — it cannot survive the client restart that makes the question worth asking. So the field now means the same thing on both sides: which process minted the pane ids in this record. `WorkspaceStore::serving_instance` picks the local daemon or the far machine's server depending on the workspace, and `finish_attempt` compares it per workspace before deciding to re-attach or rebuild. It stays client-owned: it records what *this* client last saw, so two clients on one remote workspace each keep their own and neither may overwrite the other's. An unreachable machine still records nothing, which is what keeps a good stamp from being erased with `None` — that would disarm the next check. Also in these three files: the §N references to the deleted design docs, cleaned up as part of the sweep in the following commit. * docs: drop the references to the deleted design documents The three documents this branch removed were cited ~280 times: `design §10`, `contract §8`, `§17` and friends in comments, five references by file path in code and manifests, five in CI workflows and one in the release skill. Every one of them now points at nothing. Rewritten rather than merely stripped, because most were not decoration: "design §10 makes the remote's `workspaces.json` the authority" becomes a statement in its own right, and the several that carried a Chinese phrase from the document as their justification say the same thing in English instead. Where the reference was purely parenthetical it is simply gone. Not touched: `PRD §7.1`, `brief §8` and the like, which name documents this branch did not remove and were already external before it, and the `RFC 4648 §10` test-vector citation, which is a real specification. The `host boundary` CI job loses `(§10.6)` from its name. It is not one of the required checks, so branch protection is unaffected. --------- Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> |
||
|
|
6f842c3007 |
fix(file-tree): stop a watcher event repainting a window with nothing to draw (#249)
* fix(file-tree): stop a watcher event repainting a window with nothing to draw
Issue #243 made two claims. The flicker was fixed independently on main by
|
||
|
|
9c00648875 |
fix(ui): keep the window-drag arm alive across a repaint, and make every header draggable (#252)
* fix(ui): make every header draggable, and keep the arm alive across a repaint (#221)
Two changes to the same code, which is why they land together.
Five rows that stand in for the title bar — the tab rail's top zone, the
settings page's top strip, the detail panel's top zone, and the code and
diff overlays' headers — armed their drag with an `Rc<Cell<bool>>`
allocated inside the render function. A redraw between the press and the
first drag event handed the next frame's listeners a fresh, zeroed cell
while the press had written to the old one, so the whole hold was dead
until you released and tried again.
The press itself schedules that redraw: these rows carry `on_double_click`,
and gpui calls `window.refresh()` on mouse-down for any element with a
click listener. So a drag only survived if the first move beat the next
vsync — 16ms at 60Hz, 8ms on ProMotion. A mouse press physically nudges the
pointer and often won that race; a trackpad press is a finger pushing down
without translating, and almost never did. That is the trackpad-vs-mouse
split the issue reports. The terminal's cursor blink (a 530ms `cx.notify()`
loop) disarms it on its own even with no press at all.
`window_move_gesture` now holds the flag in `window.use_keyed_state`, which
survives frames — where gpui-component's own `TitleBar` has always kept it,
and why the ordinary caption strip was never affected. Keyed rather than
`use_state` because one builder serves several call sites and `use_state`'s
`CodeLocation` id would collide when two of these rows are on screen at
once (the rail's top zone plus an overlay header is a real combination).
A longer-lived flag has to be cleared explicitly, so releasing outside the
row disarms too; with a per-frame cell the frame boundary did that for free.
Nothing else about these rows changes — same hit boxes, same geometry, same
`WindowControlArea::Drag`, same double-click.
Grabbing the window by a header is a property of the whole app, not a
per-surface feature, so a user never has to learn which rows are draggable.
Written down beside `window_move_gesture`, along with the two things it
takes beyond arming the gesture: non-controls inside a header take no hit
box (the rule #202 set for the "duo" mark, so the drag falls through them),
and a header whose contents *do* take hit boxes by design needs a floor on
its flexible spacer.
- `panel_title` — the detail panel's section header, shared by Info,
Outline, Changes, Files and the remote Files browser — is draggable now.
Its one un-`occlude()`d control (SFTP's refresh tile) gains the wrapper
every control on a drag row needs, or Windows' HTCAPTION eats its clicks.
- The horizontal tab strip keeps a bare 80px slice of caption. Its spacer
was a `flex_1` with no minimum, so it collapsed to exactly 0px once the
chips saturated the row (~7-8 tabs on a 1440px window), leaving only three
6px gaps and a hairline above and below the chips to grab — the "the
region that works seems very small" half of the report. The chip row's
fixed-chrome reserve is corrected to match: a stale flat 100px, sized when
the corner held a 30px "+" and a 30px "⋯", becomes the ~137px the corner
actually occupies plus the handle. Chips reach their minimum width and
truncate a tab or two sooner, and the window is always grabbable.
- The rail's top-zone spacer gains the same floor.
`ui::app::window_drag_tests` drives the real `title_bar_drag` row through
gpui's test platform, where `start_window_move` is `unimplemented!()` and a
panic is therefore a reliable "the window would have moved" detector. It
pins the invariant (press → repaint → move still drags), that a press alone
does not, that a release disarms, and that two rows on screen keep separate
arms. A control test keeps the old per-frame-cell pattern alongside and
asserts it still loses the drag to the identical event sequence — without
it, the invariant test could pass for the wrong reason.
* no-mistakes(review): occlude resize handles; correct chip-reserve arithmetic
* no-mistakes(document): reorder changelog sections; record non-draggable header exclusions
* no-mistakes(document): make panel grab-handle docs version-neutral and platform-accurate
* no-mistakes(document): make workspace_head panel-width doc version-neutral
* docs(changelog): re-file Unreleased entries after the rebase onto main
The rebase onto
|
||
|
|
54cf9f2a8f |
fix(ui): keep blocking host work off the UI thread and off gpui's pool
Five findings from review, all about where blocking work runs and what a stale handle is still pointing at. - `live_pane_count` ran a routed `List` — an SSH handshake, and on a WSL route as far as installing the server — straight from the Stop/Delete action handler. That is `guard_off_ui`'s debug abort in a dev build and a frozen window in a release one. It is now split into a UI-thread read and a background count, with the prompt raised through the window handle afterwards. - `teardown_workspace_forwards` blocked the UI thread on a daemon reply that waits for the SSH server to acknowledge `cancel_tcpip_forward`. On a machine that has gone unreachable — exactly when someone reaches for Stop Workspace — it never came. Backgrounded, and `on_workspace` now sets a read timeout so the thread is not parked forever either. - The file tree's and editor's watch subscriptions had no record of which host opened them. A reconnect inserts a fresh `RemoteHost` under the same `HostId`, so `set_dirs` failed on a dead `ControlClient`, was warned and dropped, and nothing opened a new one: after the first reconnect the tree stopped seeing remote changes for the life of the window, and the editor's external-change detection — what stops a save clobbering someone else's edit — was silently off. Both now compare the host by pointer and reopen when it differs. - Closing a remote window that was empty *because its machine could not be reached* deleted the workspace: its `RemoteRef`, cached layout and geometry, while its panes were still running over there. Only a machine that answered licenses dropping the entry. - `HostOps` ran blocking calls on gpui's background executor, which on Linux is a fixed pool with no blocking tier. Four stalled host calls on a four-core client took every worker, including the one the reconnect needed to clear the stall. They now run on their own elastic pool. |
||
|
|
208454e202 |
feat(remote): remote workspaces — a window that is one machine
Split the framework-free half of tty7 into `tty7-core` and add a headless
`tty7-server` built on it, so a workspace's filesystem, git and session state
can live on another machine while the GUI stays where it is.
- `crates/tty7-core`: wire protocol, session daemon, PTY, native SSH engine and
the domain model, with no gpui dependency. Module paths are unchanged.
- `crates/tty7-server`: the same daemon with no GUI attached, linked fully
static against musl and pushed onto the remote box. One dependency, on
purpose — a second one the GUI also needs belongs in core.
- `Host` trait + `HostId`/`HostRegistry`: every fs/git/watch call a workspace
makes goes through the machine it belongs to. `LocalHost` answers on this
box, `RemoteHost` over a routed control connection.
- `ui::host_ops`: the GUI's single door to a `Host`. Host calls block, so all
of them run on the background executor with the result landed on the UI
thread; de-duplication, staleness and error reporting live here rather than
at each call site. Enforced by a CI grep.
- Connect flow: home page → pick a configured SSH host → the machine's own
workspace list → a window bound to one workspace on it. Workspace switcher
groups by machine, this computer included.
- CI: static musl builds of `tty7-server` for x86_64/aarch64 via
cargo-zigbuild, a host-boundary grep, and version stamping factored out of
the nightly workflow. Both new jobs are non-required so branch protection
does not wedge open PRs.
Design and the interface contract it was built to are in
`docs/2026-07-27-remote-workspace-{design,impl-contract}.md`.
|
||
|
|
a67cf2b2ad |
feat(chrome): make the title bar's line whole off macOS
macOS fills the window's leading corner with the traffic lights and `TITLE_BAR_LEAD` reserves them 80px. Everywhere else that corner held nothing: the caption row's only contents are the rail's "+" and collapse at the rail's right edge and the corner chrome at the window's, so the left third of the row read as unfinished rather than restrained — while Windows treats the top-left as the app's identity slot. Three parts, all of them about that row: - `window_mark()` draws the "duo" mark (the app icon's own art) at the head of the rail on `CONTENT_INSET`, the line the search box and every row label below it start on, and follows the rail's controls into the title strip when the sidebar collapses. It is drawn, never clicked: no hover capsule, and deliberately no `occlude()`, so the drag region underneath still takes the press and the strip stays grabbable. - The rail's stand-in row now reserves the same hairline the real `TitleBar` draws inside its own height. Without it the bar centred content on 19.5 and the rail on 20, and the mark hopped half a pixel as collapsing the rail handed it from one to the other. - With the detail panel open off macOS the bar is hoisted above `[terminal | panel]` so the window controls can reach the corner, which left the code and diff overlays — anchored to the terminal column — starting 40px down, with headers drawn to *be* the title bar landing a row low. They now hang on the row that owns the bar, inset by the panel's width. Covering the caption row that way needs the headers to carry its gestures, which neither ever did with the panel open or closed: `title_bar_drag()` gives both (and the rail's row, which grew the same wiring by hand) drag-to-move and double-click-to-zoom, and their controls are `occlude()`d so HTCAPTION stops eating the clicks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCb8ZDmvdA5xbVtvs647tD |
||
|
|
e41afaf857 |
feat(windows): one window per workspace
tty7 had exactly one window, so `main` opened it inline and every app-wide duty — tray, menus, the quit hook — lived in `Tty7App`'s constructor. This splits those apart: a *workspace* is the persistent identity (tabs, splits, cwds, name) and a *window* is a transient view onto exactly one of them. - `ui::windows` — the app-level window registry and the single place that opens a window. Exactly one window per workspace is enforced there: the daemon gives each pane a single subscriber, so a second window on the same panes would silently steal the first's output. `open` focuses the existing window instead. New windows cascade so one never lands on top of another. - `WorkspaceStore` owns session.json, so windows never race each other as writers. Closing a window *detaches* — panes keep running in the daemon and the entry stays for the picker; `StopWorkspace` kills the sessions and keeps the layout; `DeleteWorkspace` also forgets it. - Window menu lists every workspace with a monogram badge and a liveness dot, ⌘1–9 for the first nine. Same list in the palette; closed ones also appear in a home-page picker with a coarse relative age. - Sidebar collapse and right-panel visibility move onto `Tty7App`, so toggling one window's chrome leaves the others alone; the config value becomes what a new window starts with. Panel *width* stays shared — a width is a preference, not a view state. - Tray, menus, and the quit hook now walk the registry rather than belonging to a single window. Protocol goes to v2: `RemoteKind::Wsl` is a new enum variant, which is not the additive change it looks like — the enums carry no `#[serde(other)]`, so a v1 peer fails the whole decode and drops the pane's connection. The handshake now catches that skew and offers a restart. |
||
|
|
9a4d818b78 |
refactor(editor): drop the LSP client entirely
Opening a `.rs` file in the code panel silently spawned rust-analyzer, which then indexed the whole workspace — hundreds of megabytes of RAM and a busy core — with no setting to turn it off. A terminal emulator should not do that to its user on a click, and rather than add a flag to disable something nobody asked for, the integration goes. Removed: the JSON-RPC client and reader thread (`ui::lsp`), the per-server registry, the completion / hover / definition providers installed on the buffer, document sync (didOpen/didChange/didSave/didClose), diagnostics, Go to Definition (F12), Find References (⇧F12) and its drawer, and the status bar's server indicator. With them go the `lsp-types`, `ropey` and `url` dependencies — all three were used only by this code (they remain in the lock file as transitive deps of gpui-component and gpui, which is expected). Kept, and deliberately so: - **Syntax highlighting**, which is tree-sitter, not LSP: gpui-component's `tree-sitter-languages` feature, `InputState::code_editor(language)` and `language_for_path` are all untouched. It is static, in-process, and costs nothing beyond parsing the open buffer. - ⌘S save, dirty tracking, the external-change watcher and its conflict banner, markdown preview, soft wrap, and open-from-the-file-tree. The module header now records *why* there is no language server, so the next person to reach for one finds the reasoning instead of a gap. Net −975 lines. |
||
|
|
fd1062f564 |
fix(right-panel,editor): restore the git dependency and address review findings
The branch had `gpui-component` pointed at a sibling checkout by absolute path, which is why every CI job failed at manifest load. Point it back at the fork's `tty7` branch (now carrying the custom-button label-color fix the chrome tiles depend on) with the `tree-sitter-languages` feature, and re-lock. Review fixes on top: - **Changes tab churned.** `right_panel_invalidate` dropped the cached diff on every `GitStatusCache` notification — including unrelated repos' — so the list blanked to "Loading…" and spawned a fresh `git diff` several times a second while a pane produced output. Replaced by `right_panel_refresh_changes`, which compares branch and totals first and re-probes in place, mirroring the diff overlay. - **Changes tab could wedge on "Loading…".** A probe dropped because the cwd changed mid-flight left `diff_cwd` set and `diff` empty, and the render path only spawns when the cwd *changes* — so nothing re-probed. Spawn when nothing is cached and nothing is in flight. - **Find references blocked the UI thread.** `cx.spawn_in` runs on the main thread; the up-to-200 `read_to_string`s for the row previews now run on the background executor, as the comment already claimed. - **LSP frames could be lost or reordered at startup.** `send` checked `ready` outside the `queued` lock, so a frame could park behind a handshake that had just finished and never go out. `ready` now flips under that lock in `mark_ready_and_flush`. - `MarkScanner`'s ESC-in-payload branch bypassed the payload cap, so a stream of bare ESCs inside an unterminated OSC grew the buffer without bound. - The file tree's search frontier used `Vec::remove(0)`; a wide tree made that quadratic. `VecDeque`. - `procs()` documented a pane check it didn't make; it takes the pane id and makes it. - Four doc comments had been orphaned onto newly inserted functions (`pty`, `smooth_scroll`, `foreground_agent`, `file_expanded`). |
||
|
|
403cfd47a1 |
feat(right-panel): docked detail panel with Info, Changes and Files tabs
Add a right-hand detail column showing what the active pane is, not what it prints: session facts plus its process tree and listening ports (daemon-side procinfo, pull-based via QueryProcs), the working-tree diff, and the file tree. Tab row lives in the title bar, body in right_panel. Also record OSC 133 command marks client-side so the panel's Outline can list a pane's commands and scroll back to one, keyed on row text since absolute scrollback indices drift once history fills. |
||
|
|
2f1978618d |
refactor(code-panel): per-tab panel state, diff-overlay style
The panel's open files, tree roots/expansion/selection, and visibility now live on Tab.code (same contract as Tab.diff_overlay): only the active tab's panel renders, switching tabs shows that tab's own panel (or none), and closing the tab drops its state. Hiding via Esc keeps the tab's open files. Shared infrastructure stays app-global: directory-listing and gitignore caches (path-keyed, tab-agnostic), the LSP registry, and single watchers over the union of every tab's roots / open files. External-change reloads and diagnostics now fan out to every buffer of the path across tabs. |
||
|
|
f9ed31c0af |
refactor(code-panel): full-body overlay instead of docked side columns
The file tree + editor now render as one overlay covering the terminal (settings/diff-overlay style): toggling never resizes the terminal (no PTY resize/reflow) and the editor gets the full body width. The tab sidebar stays visible and switching tabs re-roots the tree; focus follows the panel. - Merge ToggleFileTree/ToggleEditor into one ToggleCodePanel action (cmd-shift-e, Esc closes, palette "Code Panel"). - Add the one on-screen entry point: a title-bar tile next to the overflow menu, lit while the overlay is up (present in both tab-bar modes). - Drop the editor width divider and the file tree's standalone open flag. |
||
|
|
acb461a094 |
feat(code-panel): local file tree, code editor panel, and LSP client
- File tree (left column): lazy per-directory listing with notify-driven refresh, gitignore chain matching (dimmed italics), keyboard nav, inline new-file/new-folder/rename, context menu (open / cd / insert path / attach-to-agent / copy path / reveal / delete), multi-root from the active tab's pane cwds, rows draggable into the terminal as ExternalPaths. - Code editor (right column): gpui-component CodeEditor mode (tree-sitter highlighting, line numbers, folding, find/replace), file tabs with dirty markers, cmd-S save, external-change reload with conflict banner, markdown preview, soft-wrap toggle. - LSP: stdio JSON-RPC client per (server, workspace root) for rust-analyzer / gopls / pyright / tsserver / clangd; completions, hover, diagnostics, same-file cmd-click definitions, F12 cross-file goto, shift-F12 references drawer. |