Skip to content

feat: Send to Terminal — selection, file, and commit refs#114

Open
jonasnobile wants to merge 7 commits intomainfrom
feat/send-to-terminal
Open

feat: Send to Terminal — selection, file, and commit refs#114
jonasnobile wants to merge 7 commits intomainfrom
feat/send-to-terminal

Conversation

@jonasnobile
Copy link
Copy Markdown
Member

Summary

Adds a "Send to Terminal" feature across the file viewer, diff viewer, and commit graph. Selecting code in either viewer reveals a floating button that pastes a path:N-M header + fenced code block into the focused terminal. Right-click menus on file-tree rows and tabs send the bare path; right-clicks on commit-hash badges and commit-graph rows send a git log-style block (hash + refs + author + relative date + indented subject).

Targets the focused terminal regardless of project (local or remote — the Terminal abstraction's transport handles routing). Always wraps payloads in DECSET 2004 brackets so multi-line content lands as one editable paste in zsh / bash / fish / Claude / Codex, instead of executing line-by-line. Strips trailing newlines so receivers don't auto-submit at end-of-paste.

Entry points

  • Selection in file viewer or diff viewer → floating button → fenced code block (one per file in multi-file diff selections).
  • File-tree right-click (both viewers) + tab right-click (file viewer) → "Send to Terminal" → path .
  • Commit hash badge (diff viewer header + commit info bar) → right-click → menu with "Send to Terminal" + "Copy Hash".
  • Commit graph row → right-click → same menu (left-click still opens the diff viewer).

Architecture

  • Viewer events bubble up via existing cx.subscribe chains into a new OverlayManagerEvent::SendSelectionToActiveTerminal (for selections) or directly via a new top-level OverlayRequest::SendTextToActiveTerminal (for the commit graph, which doesn't sit under OverlayManager).
  • RootView::send_text_to_active_terminal resolves the focused terminal via focus_manager.focused_terminal_state() + LayoutNode::get_at_path(), then calls Terminal::send_paste_force_bracketed().
  • Pure formatting helpers live in okena-files::send_payload and okena-views-git::commit_send. Floating button extracted to okena-ui::floating_send_button.

Tests

25 new unit tests covering the four pure functions:

  • markdown_lang_hint (6) — extension mapping, unknown ext fallback, multi-dot paths.
  • format_send_block (5) — single-line vs range header, no-trailing-newline invariant.
  • format_commit_send_text / format_commit_entry (7) — refs, full layout, multi-line message, no-trailing-newline invariant.
  • build_selection_segments (7) — same-file merging, file boundaries, all-skipped, alternating files.

Notes for review

  • Branch is based on a stale local main (11 commits behind origin) — there will be merge conflicts against the upstream feat: detachable file viewer and diff viewer overlays commit which touched the same files. Rebase before merging.
  • Three files in crates/okena-views-git/src/diff_viewer/ (scrollbar.rs, syntax.rs, types.rs) had pre-existing local edits that aren't part of this work and were intentionally left out of the commits — they remain as uncommitted changes in the working tree.
  • "Always bracket" means receivers that don't speak bracketed paste (rare modern shells, cat-style readers) will see literal \x1b[200~ and \x1b[201~ bytes. Acceptable failure mode vs. multi-line content executing line-by-line.

Test plan

  • In an empty zsh terminal, right-click a commit hash in the diff viewer header and click "Send to Terminal" — block lands as one editable paste, Enter only submits when you press it.
  • In a Claude/Codex terminal, same flow — block appears in the prompt as multi-line text, no auto-submit.
  • Select multiple lines in the file viewer, click "Send to terminal" button — path:N-M + fenced block appears in active terminal.
  • Multi-file selection in unified diff — produces one fenced block per file.
  • Right-click a file in the file tree → "Send to Terminal" → just the path lands.
  • Right-click a commit row in the graph popover → menu shows; "Copy Hash" copies the short hash; "Send to Terminal" pastes the block.
  • No focused terminal → toast warning appears.
  • Remote project → still works (uses the same terminal-registry path).
  • cargo test — 25 new tests pass.

🤖 Generated with Claude Code

@jonasnobile jonasnobile force-pushed the feat/send-to-terminal branch 2 times, most recently from f542969 to 5d6a9f3 Compare May 4, 2026 16:50
jonasnobile and others added 7 commits May 7, 2026 13:13
…x", test)

The macOS lsof parser and Windows netstat parser were already cfg-gated; the ss
parser and its helpers weren't, so dead-code warnings fired on every non-Linux
build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…racketed

send_paste used to convert all newlines to CR before wrapping in DECSET 2004
brackets. CR inside a bracketed paste reads as Enter to receivers like zsh's
zle and Claude/Codex TUIs, so multi-line clipboard pastes auto-submitted line
by line. Inside a bracketed paste, LF should land as a literal newline; the
\n→\r conversion is now scoped to the non-bracketed fallback path.

Adds send_paste_force_bracketed for programmatic paste paths (e.g. "Send to
Terminal") where the alacritty-tracked DECSET flag is unreliable: multiplexers,
prompt frameworks that toggle the mode, and fresh terminals before the shell's
startup sequence is processed all cause BRACKETED_PASTE to read false even when
the receiver supports it. Receivers that don't support bracketed paste see
the bracket bytes as literal text — recoverable, vs. multi-line content
executing each line as a separate command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…utton

- okena-files::syntax::markdown_lang_hint — file extension → fence label.
- okena-files::send_payload::format_send_block — path:N-M header + fenced
  block, no trailing newline (Claude/Codex TUIs read trailing LF inside a
  bracketed paste as Enter).
- okena-views-git::commit_send::format_commit_send_text /
  format_commit_entry — git-log-style commit reference.
- okena-ui::floating_send_button::floating_send_to_terminal_button —
  deferred + anchored "Send to terminal" panel, used by both viewers.

Includes unit tests for the four pure functions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New OverlayRequest::SendTextToActiveTerminal { text } so views with access
  to the request broker (commit graph, file/diff viewers) can paste into the
  focused terminal without owning a Terminal handle.
- New OverlayManagerEvent::SendSelectionToActiveTerminal, re-emitted by the
  three FileViewer/DiffViewer subscribe blocks when their viewers fire
  SendToTerminal.
- RootView::send_text_to_active_terminal resolves the focused terminal_id
  via focus_manager + LayoutNode::get_at_path and calls
  Terminal::send_paste_force_bracketed. Toasts a warning if no terminal is
  focused.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Selection: floating "Send to terminal" button anchored at mouse-up over a
  non-empty selection; click emits a path:N-M header + fenced code block.
- Whole file: "Send to Terminal" entry in the file-tree right-click menu and
  the tab right-click menu, sending just the path (project-relative when the
  file is in the project list, otherwise absolute).

Path is project-relative when the file is in the project list, otherwise
absolute.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Selection: floating "Send to terminal" button over a non-empty selection.
  Multi-file selections produce one fenced block per file (deleted/binary
  files skipped, header/expander rows skipped).
- Whole file: "Send to Terminal" entry in the file-tree right-click menu.
- Commit hash badge (header + commit info bar): right-click for a small menu
  with "Send to Terminal" + "Copy Hash". Send pastes a git-log-style block
  (hash + refs + author + relative date + indented subject).
- Commit graph row: right-click for the same menu; routes through the new
  OverlayRequest::SendTextToActiveTerminal.

Selection-to-segments uses a shared build_selection_segments helper that
groups adjacent same-file lines and opens new segments at file boundaries —
unified mode tracks files via a running cursor over file_offsets (O(N+F)),
side-by-side via the nearest preceding file_header decoration. Selection
button anchor lifecycle is funneled through clear_selection_state(); render
predicate is the cheap selection.normalized_non_empty(), the click path
revalidates and silently no-ops if the selection covers only headers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…crollbar

- types.rs: add `file_index: usize` to ExpanderRow and a FileHeader variant to
  DisplayItem so continuous-scroll items can be associated with their owning
  file (used by selection segmenting).
- syntax.rs: process_file now takes a file_index and threads it into the
  expander rows it builds.
- scrollbar.rs: drop the per-file effective_view_mode override — it was
  forcing unified mode for new/deleted files in side-by-side, which the
  selection-segmenting path didn't need to special-case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jonasnobile jonasnobile force-pushed the feat/send-to-terminal branch from 5d6a9f3 to 99603d1 Compare May 7, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant