Smart shell copy
A terminal block in Starlight often shows a session: commands after a prompt, and the output they print. Readers who copy that block get the prompts and the output too, and the paste fails in their shell. Smart shell copy reads the prompts, and adds a Copy commands button to the title bar. The button copies the commands only.
Readers see
$ uv tool install ruffResolved 1 package in 180msInstalled 1 executable: ruff$ ruff check src/ \ --fixFound 3 errors (3 fixed, 0 remaining).You write
```sh frame="terminal"$ uv tool install ruffResolved 1 package in 180msInstalled 1 executable: ruff$ ruff check src/ \ --fixFound 3 errors (3 fixed, 0 remaining).```The two lines that start with $ are commands, and --fix continues the command above it. The other lines are output, in a muted colour. Copy commands copies the two commands without the prompts, with the continuation line kept. The copy button copies the whole block, as it does in every other block.
Syntax
Section titled “Syntax”Smart shell copy has no directive. It applies to every block with a terminal frame that has at least one prompt line.
| Block | Terminal frame |
|---|---|
A shell language, such as sh, bash, zsh or powershell |
Yes, from Expressive Code’s automatic frame |
Any language with frame="terminal" |
Yes |
A shell language with frame="code" or frame="none" |
No |
A prompt is text at the start of a line. The default prompts are $ and > , each with a space after it. To use other prompts in one block, add shellCopy.prompts="<text>" to the fence line, once for each prompt, such as shellCopy.prompts="% ". The fence line is the first line of the code block, with the language. These prompts replace the defaults for the block.
A pycon block with a line that starts with >>> is a Python session. A python or py block is a session when its first line starts with >>> . It gets the same button, and keeps the editor frame. See Python sessions.
Examples
Section titled “Examples”A custom prompt
Section titled “A custom prompt”A PowerShell session uses its own prompt. This site adds PS> to shellCopy.prompts, so Copy commands in the block below copies two commands:
PS> Get-ChildItem -Namereport.csvsummary.txtPS> Get-Content summary.txtRows: 1204```powershellPS> Get-ChildItem -Namereport.csvsummary.txtPS> Get-Content summary.txtRows: 1204```A session in any language
Section titled “A session in any language”A block in any language can show a session, if it has frame="terminal":
$ python -m pip install example-clientCollecting example-clientSuccessfully installed example-client-2.1.0```txt frame="terminal" title="Install the package"$ python -m pip install example-clientCollecting example-clientSuccessfully installed example-client-2.1.0```Behaviour
Section titled “Behaviour”- Commands and output:
- A line that starts with a prompt is a command. The prompt has its own colour, and a manual selection leaves it out.
- A command line that ends with
\continues on the next line. That line is part of the command too. - Every other line is output. Output lines have no syntax colours and use a muted colour.
- A terminal block with no prompt line renders and copies as Expressive Code shows it.
- Copying:
- The Copy commands button in the title bar copies each command without its prompt, one command on each line. After a copy, its label is Copied for a short time, and screen readers announce “Copied”.
- The copy button works as in every other block. It copies the whole block: the prompts, the commands and the output.
- Hidden lines that are commands are part of the text that Copy commands copies. Hidden output lines are not.
- Without JavaScript:
- The two buttons need JavaScript, as in Expressive Code. Without JavaScript, the Copy commands button is hidden, and readers can select the code by hand. The selection leaves out the prompts.
Python sessions
Section titled “Python sessions”A Python session in a docstring or a tutorial shows commands after >>> and the values that they print. Smart shell copy reads these blocks with no configuration and no frame="terminal":
Readers see
>>> from collections import Counter>>> counts = Counter(["ok", "ok", "failed"])>>> for status, n in counts.most_common():... print(f"{status:<8} {n}")...
ok 2failed 1You write
```py>>> from collections import Counter>>> counts = Counter(["ok", "ok", "failed"])>>> for status, n in counts.most_common():... print(f"{status:<8} {n}")...ok 2failed 1```Copy commands copies the three commands without the prompts, and keeps the two continuation lines. The rules follow the Python REPL:
- A line that starts with
>>>is a command. - A line that starts with
..., or a bare..., continues the command while its statement is open. A compound statement stays open until a bare...line. It starts with a keyword such asdef,class,if,fororwith, or with a@decorator. Any other statement stays open while it has an open bracket, a trailing\or an open triple-quoted string. - After a complete statement, a line that starts with
...is output, for example the text thatprint("...")shows. - Every other line is output.
API auto-linking links names in the commands of a session, and not in its output.
Options
Section titled “Options”shellCopy
Section titled “shellCopy”Adds a Copy commands button to terminal blocks with prompts, which copies the commands only. Set it to false to turn the feature off.
- Type
false | object- Default
- On
shellCopy.prompts
Section titled “shellCopy.prompts”Line starts that mark a command. A code block can set its own on its fence line.
- Type
string[]- Default
['$ ','> ']
The default prompts do not include #, because # also starts a comment in shell code. If your examples use a root prompt, add it:
codeblocks({ shellCopy: { prompts: ['$ ', '# '] },});With shellCopy: false, terminal blocks have no Copy commands button, and prompts show as part of the code.
Limitations
Section titled “Limitations”- A prompt must be the first text on the line. The plugin does not find a prompt after spaces.
- Output lines lose their syntax colours, so output that contains code shows in one colour.
#is not a default prompt. A line such as# apt install curlis output, unless you add#to the prompts.- In a Python session, a line that starts with
>>>is always a command, also if it is output that a command printed.
Related
Section titled “Related”- Hidden lines: hide set-up commands that readers need to copy but not to read.
- Fill-in placeholders: let readers type their own values into a command before they copy it.