Skip to content

feat(app): add a command editor to the Commands tab - #163

Merged
TooonyChen merged 2 commits into
mainfrom
feature/command-editor
Nov 9, 2026
Merged

TooonyChen merged 2 commits into
mainfrom
feature/command-editor

Conversation

@TooonyChen

@TooonyChen TooonyChen commented Nov 9, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds FinalShell's command editor to the Commands tab: a pane for writing a few lines and sending them without saving them as a quick command first.

  • A "Command editor" toggle in the toolbar, after "New command", shows or hides the pane. It looks pressed while the pane shows. The pane sits on the right of the buttons, and the groups keep the tab's left side. A divider sets its width: 280 DIPs at least, and at most what leaves the buttons 220. A double click resets it to 460. Where the tab is narrower than 670 DIPs, as in the right column (feat(app): put the bottom panel on the right of the terminal #158), the pane goes under the groups and the buttons at the tab's full width. A vertical divider then sets the buttons' share of the height (40% by default). Whether the pane shows and both sizes are saved with the pane sizes (commandEditorShown, commandEditorWidth, commandEditorPercent), local only.
  • The text uses ui/editor, the same editor as the remote file editor, not ui.TextArea. It gives line numbers, as the mockup shows, and selections. Its rope also lays out only the lines in view, so a large text stays responsive. The text is split into lines only when it is sent. Highlighting, auto-closing brackets, folding and whitespace markers are off. Enter inserts a new line, Tab indents, and Esc gives the keyboard back to the terminal, as the command bar does. In a panel row, Esc does nothing.
  • "Send", or Cmd+Enter (Ctrl+Enter elsewhere) in the text, sends every line the selection touches, whole, or all of the text when nothing is selected, so a sloppy drag never sends half a command. A selection that ends at column 0 of a line (a drag to the start of the next line) does not include that line. Several selections on the same or neighboring lines are joined. The button says "Send selection" while there is a selection. Sending works like sendQuick: config.QuickCommandLines, then each line through sendLine. Lines with control characters are sent as they are, as quick commands send them.
  • "Send to" reuses quickTarget, which now takes an anchor ("quick" or "editor") so each control gets its own Choose sessions popover. It is the same a.sendTo state, and the send goes through sendToTargets, so it has the same warning color and the same first-send question. While the editor shows, the toolbar hides its own Send to, because the editor's control sets the same target.
  • While the editor shows, the toolbar's search placeholder is the short "Search" / "搜索" (new key quickSearchShort), because "Search commands" was clipped beside the editor at default widths. Its accessible name stays "Search commands".
  • "Options" has "Press Enter after each line" (on by default) and "Clear after sending" (off by default), saved as commandEditorNoEnter and commandEditorClear, local only. Clear removes only what was sent: the selected lines, whole, with their newlines, or everything. It is skipped if the text changed while the question was open, and Cmd+Z brings the text back. After a send the editor keeps the keyboard. With Enter off, the terminal takes the keyboard instead so you can press Enter there.
  • "Save as command" opens the quick command editor window for a new command. The text is the selected lines, whole as Send takes them, or everything, and Enter follows the editor's option. "Clear" empties the text and can be undone.
  • Send is disabled, and Cmd+Enter does nothing, when no session of the target is connected or the text is empty.
  • The text is kept in commandeditor.txt in the config folder. It is not synced and is the same for every session. It is written with the atomic write helper one second after typing stops, and again in CloseAll. If the file cannot be read, the pane shows a banner and nothing is written over the file.
  • Other changes:
    • The toolbar now wraps when it is narrow. Before this, its Send to and search overflowed in the right column.
    • The "split sized from its end edge" trick moved from withRightColumn into endSplit in splits.go, which both callers use.
    • The README's quick-commands scene now shows the editor open with a selection, and the README text has one new sentence about the editor.

Design decisions to check: the toolbar's Send to is hidden while the editor shows (the mockup shows both, but at its width they don't fit beside the editor). The editor is hidden by default. The Send button shows its shortcut as small text instead of an icon.

Closes #160

Testing

  • go fmt ./... (no changes), go build ./..., go vet ./..., GOOS=windows go vet ., GOOS=darwin go vet ., go tool mygo vet and go test -race ./... all pass on main with fix(sshx): keep idle connections alive with SSH keepalives #162.
  • New tests in internal/app/cmdeditor_test.go:
    • The toggle: its pressed state, saving, and reading the setting back in a new App.
    • Dragging the divider, its limits, saving and reading back, and a double-click reset.
    • Send with all lines and with a selection. Ctrl+Enter, Enter as a new line, Tab, Esc to the terminal, and the history entries.
    • Whole lines (TestCmdEditorWholeLines, TestCmdSelectedLines): a partial selection across two lines sends both lines whole, a backwards selection inside a line sends that line, a selection ending at column 0 leaves that line out, Save as command takes the same lines, and Clear after sending removes the whole lines and undoes. A table covers single and multiple selections, a selection of just a newline, the last line and the whole text.
    • The search placeholder (TestCmdEditorSearchPlaceholder), in English and Simplified Chinese: the long one without the editor, the short one beside it, and its measured width fits the field.
    • The Options: Enter off and Clear after sending (selection and everything), their saving, undo, and Clear.
    • Target All through the editor's Send to: the question, each connected session once, and the panel row.
    • Disabled while disconnected, in a new tab, and while empty.
    • The text surviving a restart (a new App reads the file) and being saved on CloseAll. An unreadable file is not overwritten.
    • Save as command opening the window filled in.
    • The stacked layout in the right column, its saved share, and the switch back beside the buttons when the column is wide.
  • Screenshots in English and Simplified Chinese, from SHOT_DIR=… go test ./internal/app -run TestCmdEditorShots, checked against the mockup:
    • cmdeditor-selection (open with a selection) and cmdeditor-partial (a partial-line selection)
    • cmdeditor-options (the Options menu)
    • cmdeditor-target-all and cmdeditor-confirm (target All and its question)
    • cmdeditor-right-column and cmdeditor-right-column-hidden (the Commands tab in the right column)
    • cmdeditor-hidden
  • go run ./tools/screenshots regenerated docs/screenshots/quick-commands-{en,zh-CN}.png. The other scenes came out unchanged.
  • Docs: features.md has a new "Command editor" subsection and a note under Layout. configuration.md has the file, the keys and the local settings list. development.md has the file layout. All in both languages, with the same headings.
  • Not tested:
    • The real app on macOS or Windows. The ⌘↩ label and Cmd+Enter on macOS were not tried.
    • Very large texts beyond a quick manual reasoning about the rope editor.
    • TestSSH against a real server.

Generated by Claude Code

A Command editor pane beside the command buttons, or under them where
the tab is narrow: write a few lines and send them, or the selection,
line by line through sendLine to the window's send target, with
Cmd/Ctrl+Enter. Options for Enter after each line and clearing after
sending; Save as command opens the quick command editor filled in. The
text is kept in commandeditor.txt in the config folder.

Closes #160

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015nMqJHsqtndDSqJDPp1qYv
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T23:19:19.112838Z a0d3e24 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0d3e24a65

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/app/cmdeditor.go
Comment on lines +346 to +349
a.sendToTargets(s, send, func() {
if a.settings.CommandEditorClear && buf.Version() == version {
if len(sent) == 0 {
buf.Set("")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear the editor only after sending to at least one session

With “Clear after sending” enabled, if all target sessions disconnect while the first-send confirmation is open, confirming still clears the draft even though nothing was sent. In sendtarget.go, sendToTargets skips disconnected sessions but invokes its completion callback unconditionally, and the confirmation dialog leaves Send enabled. Track whether the send callback actually ran and require that before removing the text, so unsent commands remain available to retry.

Useful? React with 👍 / 👎.

A selection in the command editor now sends, saves as a command and,
with Clear after sending, removes every line it touches, whole, so a
sloppy drag never sends half a command. A selection ending at the start
of a line leaves that line out.

Beside the editor the Commands toolbar's search was too narrow for its
placeholder, so it now says just "Search" (搜索) while the editor shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015nMqJHsqtndDSqJDPp1qYv
@TooonyChen
TooonyChen merged commit fb6ff2d into main Nov 9, 2026
1 check passed
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.

feat(app): add a command editor to the Commands tab

2 participants