docs/features/terminal.md
Fresh includes a built-in terminal emulator.
All from the command palette (Ctrl+P):
Alt+backtick) — a terminal in the shared bottom dock, creating the dock if needed.You can open multiple terminal tabs and switch between them like regular file buffers. The tab bar's + button also offers New Terminal / New File.
Send Selection to Terminal runs the selected text — or the current line if there's no selection — in the most recently used terminal, and switches focus to it.
Ctrl+Click (or Ctrl-hover, which underlines the target) opens file paths from terminal output, including scrollback. Paths resolve as absolute (with ~ expansion), then relative to the terminal's working directory, then relative to Fresh's working directory. The shell's working directory is tracked via OSC 7, so relative paths resolve correctly after cd — and resolution works over SSH.
When a terminal's process quits, the tab is marked claude (exited) and the buffer stays open as read-only scrollback, frozen exactly on the process's last screen — nothing is written into the output, so the top of that screen isn't pushed out of view. The status bar shows a clickable restart indicator on the bottom-left — ⟳ Restart terminal, or ⟳ Restart npm / ⟳ Resume claude named after whatever was running. A non-zero exit code rides along: ⟳ Resume claude (exit 3).
Restart it three ways — all equivalent:
The process comes back in the same buffer, appending below the existing transcript, so what was there stays where it was and you don't have to open a new terminal and lose your place. The tab drops its (exited) marker. This is the same mechanism that reattaches a workspace's terminals when you reopen the editor, now available per buffer and on demand — including the same argv precedence:
claude --resume <id>), so a coding agent rejoins its conversation rather than starting a fresh one;Agent resume obeys the terminal.resume_agents setting (default on); turn it off to always re-run the launch command instead.
The offer survives the editor, too: quit with a dead terminal and the workspace comes back showing that pane's transcript with the indicator still on it. It comes back dead on purpose — reopening a workspace never silently re-runs a process you had finished with, which for an agent would mean resuming the conversation (and spending tokens) unasked. The restart stays one click away.
A running terminal is never restarted — the indicator only appears once the process has quit, and asking anyway (from the palette while the split sits in scrollback) reports "Terminal process is still running" rather than killing what's there.
The indicator is the {terminal_restart} status-bar element and can be moved or removed via editor.status_bar.left / .right like any other.
The terminal has two modes, indicated in the status bar:
Terminal Mode (status bar shows "Terminal"): Your keyboard input goes directly to the shell. The scrollbar is hidden and the grid uses the full split width.
Scrollback Mode (status bar shows "Terminal (read only)"): The terminal output becomes a read-only buffer that you can scroll through, search, and copy text from.
Per-split: The mode is a property of each split, not the terminal as a whole. If you show the same terminal in two splits, one can sit in scrollback (frozen at some point in history) while the other keeps streaming the live output — and each split keeps its own mode even when it isn't focused. Disable Automatic Scroll (below) to hold a split in scrollback while output keeps arriving.
Ctrl+Space: Toggle between terminal mode and scrollback modeCtrl+]: Exit terminal mode (same as Ctrl+Space)By default, most editor keybindings (like Ctrl+P for command palette) still work in terminal mode. If you need to send these keys to the terminal instead:
F9: Toggle keyboard capture modeF9 are sent to the terminalIn scrollback mode, you can use standard editor navigation:
Ctrl+Home: Jump to the beginning of scrollback historyCtrl+End: Jump to the endCtrl+F: Search through terminal outputBy default the integrated terminal runs $SHELL (or the platform default if $SHELL is empty). Override this with terminal.shell in config without changing $SHELL for the whole process — handy when you want a wrapper that forces an interactive shell, or a different shell inside a container:
{
"terminal": {
"shell": { "command": "/usr/bin/bash", "args": ["--login", "-i"] }
}
}
The override applies to host-shell terminals; wrappers that re-parent the shell keep their own spawn contract and ignore terminal.shell. This includes docker exec when attached to a devcontainer and the ssh -t wrapper used when editing a remote over SSH — in those cases the terminal opens the shell inside the container / on the remote host, rooted at the workspace.
fresh -a to start in daemon mode, then detach with Ctrl+Shift+D to keep terminal processes running in the background. Reattach with fresh -a. See Daemon Mode for details.terminal.jump_to_end_on_output config option — handy when you want to read scrollback in one split while another split (or the same one) keeps streaming. The jump always yields while a text selection is active, so new output can't destroy a selection you're about to copy.Ctrl+Space resumes) with a real selection that Ctrl+C copies; a bare click still just focuses the terminal, and double-click selects words in scrollback. Set terminal.mouse_drag_selects = false to make drags on the live grid inert again.Shift+drag bypasses it so you can always select. Set terminal.mouse_forwarding = "alt_screen" to restore the legacy rule (forward everything to any alternate-screen program, no Shift bypass).Fresh supports full terminal emulation on Windows 10 (1809+) via ConPTY. PowerShell is preferred over cmd.exe. Stdin piping works with type file | fresh.