-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Add Terminal History via OSC Sequence Shell Injection #16732
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add Terminal History via OSC Sequence Shell Injection #16732
Conversation
432c0ed to
72f85f6
Compare
sgraband
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the contribution @Fangoling! This is already in a great state and works well (in the browser example)! I left some inline comments and questions. Could you take a look at those?
Note: I haven't reviewed the OSC13 parsing and the bash/zsh files.
packages/terminal/src/node/shell-integrations/bash/bash-integration.bash
Show resolved
Hide resolved
sdirix
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started my review yesterday so some of my comments overlap with the ones of @sgraband. Please have a look.
packages/terminal/src/node/shell-integrations/bash/bash-integration.bash
Outdated
Show resolved
Hide resolved
packages/terminal/src/node/shell-integrations/bash/command-block-support.bash
Outdated
Show resolved
Hide resolved
packages/terminal/src/node/shell-integrations/zsh/zdotdir/.zlogin
Outdated
Show resolved
Hide resolved
packages/terminal/src/node/shell-integrations/zsh/zdotdir/.zprofile
Outdated
Show resolved
Hide resolved
packages/terminal/src/node/shell-integrations/bash/bash-integration.bash
Outdated
Show resolved
Hide resolved
sgraband
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the changes @Fangoling! Just one small inline comments, and some comments on other threads from my side.
@sdirix Do you want to take another look?
|
I will review at the latest next week. |
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
…when no new line is provided Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
… to shell during injection Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
able to capture all commands regardless of execution timing Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
…ug command Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
923e425 to
69cf24d
Compare
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
I added the preference for terminal command history that you requested:
I also added the missing support for task terminals, since these do not use an interactive shell I am not able to reuse the shell injector. (added it to the pr description as well, after the package static resource section)
|
sdirix
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (this.getEnableCommandHistory()) { | ||
| const commandHistory = this.terminalWidget.commandHistory; | ||
| const lastCommandBlock = commandHistory.at(-1); | ||
| if (lastCommandBlock) { | ||
| return [lastCommandBlock.command, lastCommandBlock.output]; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getRecentTerminalCommands is intended to return the last visible lines, not only the latest command. Should we return 100 lines worth of the latest commands and outputs we have?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could have it return at least 100 lines or at least one command. I think this would make the most sense, as it would prevent long command output being cutoff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am fine with returning more than 100 lines, however the functionality which processes this information and injects it into a prompt needs to make sure to process overly long outputs as otherwise the prompt may blow up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a context translator that takes 1200 characters worth of output and returns it.
When a command is out of scope it will be cut off at the end instead of at the start (when using the 100 lines heuristic).
| // Inject command_started OSC when task begins | ||
| const encoded = Buffer.from(command).toString('hex'); | ||
| this.ringBuffer.enq(`\x1b]133;command_started;${encoded}\x07`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this also react on the preference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I implemented passing of the preference from the terminal service to the task-terminal-process.ts by modifying the TaskConfiguration Interface.
I am not sure if that is something that I should be modifying, the alternative would be to modify the signature of the run method in process-task-runner.ts which is also not ideal.
packages/terminal/src/node/shell-integrations/zsh/zdotdir/source-original.zsh
Outdated
Show resolved
Hide resolved
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Thanks for the review! Regarding your error: The script that JetBrains provides that we are using for this functionality does not work when you are using the Powerlevel10k theme. Here is an issue related to this https://youtrack.jetbrains.com/issue/IJPL-178955. The original scripts disables support completly and does not source the integration script to begin with. I removed it back then as I thought that it was just an edge case, but apparently a lot of people use this theme. Until that is fixed we could find our own solution or add this as a follow up issue. |
That's fine for me. Let us readd the check and just behave the same as Jetbrains does. If they manage to fix it at some point we can add the fix too. |
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Signed-off-by: Fangxing Liu <fx.liu@tum.de>


What it does
This PR introduces Terminal Command History support to the Theia Terminal extension.
Each terminal widget now maintains an internal command history buffer that records discrete command blocks:
To support this, the terminal now exposes two new events:
These events allow extensions to reliably detect when a prompt appears and when a command begins.
Additionally, this PR adds:
Issue
The xterm.js terminal emulator does not expose APIs for retrieving past commands or outputs.
Extensions such as AI Terminal currently extract a fixed number of buffer lines, which leads to issues:
Solution
We adopt the approach used in the new JetBrains terminal architecture by injecting OSC 133 sequences at the shell level.
These sequences indicate when prompts and commands begin and allow the terminal to reconstruct accurate command blocks.
The terminal registers an OSC 133 handler and responds to different payloads to build the history.
Because shells do not emit a “command finished” marker, the next appearing prompt implicitly finalizes the previous command block.
Shell injection
Shell integration must occur inside the user’s shell environment.
To avoid overwriting user config, we follow the JetBrains strategy and load our integration scripts as wrappers:
For zsh:
.zlogin,.zprofile,.zshenv,.zshrcZDOTDIR)For bash:
.bashrcrequires sourcingA new ShellIntegrationInjector:
Package static resources
CopyWebpackPluginto copy the shell integration scripts into the lib folder of the packaged applicationTask Terminal Support
Task terminals execute non-interactive commands (
bash -c "command") and cannot use shell integration hooks.For these terminals, we inject OSC 133 sequences directly in the
TaskTerminalProcess:command_startedis emitted when the task begins executionprompt_startedis emitted when the task exits or is killedThis approach is shell-agnostic and works consistently across all platforms (bash, zsh, cmd, PowerShell).
Unlike interactive terminals that rely on shell hooks, task terminals use direct OSC injection since they
execute commands in non-interactive mode where
.bashrcand integration scripts are not sourced.Know issues
Debug sessions: When a debug session runs a command, the echoed command appears both as input and output.
This occurs because we can only detect the start of a command, not the start of its output. User commands can be detected by tracking the press of the Enter key.
Z-index behavior: Command block separators are rendered above the AI terminal widget.
This is also how other terminal decorations behave (e.g., selected text).
Modified files
The shell integration scripts are based on scripts from the JetBrains IntelliJ community repository, which were adapted and modified to fit our use case.
You can find them in
/packages/terminal/src/node/shell-integrations/zsh/zsh-integration.zsh:JETBRAINS_INTELLIJ_ZSH_DIR->THEIA_ZSH_DIR._INTELLIJ_FORCE_SET_*/_INTELLIJ_FORCE_PREPEND_*handling, plus the one-timeprecmd_functionshook injection).zsh/command-block-support.zsh__jetbrains_intellij_*to__theia_*.INTELLIJ_TERMINAL_COMMAND_BLOCKS_REWORKEDandP9K_VERSION(integration always loads).OSC 1341;...to TheiaOSC 133;...and changedcommand_startedformat to send only the encoded command (nocommand=...key).command_finished+current_directoryreporting and removed alias reporting (aliases_received).zsh/zdotdir/.zlogin, .zprofile, .zshenv, .zshrc, source-original.zsh
JETBRAINS_INTELLIJ_*toTHEIA_*.JETBRAINS_INTELLIJ_TERMINAL_DEBUG_LOG_LEVEL.bash/bash-integration.bashJETBRAINS_INTELLIJ_BASH_DIR->THEIA_BASH_DIR,__jetbrains_intellij_restore_posix_flag->__theia_restore_posix_flag)._INTELLIJ_FORCE_SET_*/_INTELLIJ_FORCE_PREPEND_*processing), plus optional sourcing ofJEDITERM_USER_RCFILEandJEDITERM_SOURCE(+ args).__INTELLIJ_COMMAND_HISTFILE__+ EXIT trap).command-block-support.bash(nocommand-block-support-reworked.bash), without readability checks.bash/command-block-support.bash__jetbrains_intellij_*to__theia_*.INTELLIJ_TERMINAL_COMMAND_BLOCKS_REWORKED) so the integration always loads.OSC 1341;...to TheiaOSC 133;...and changedcommand_startedpayload to emit only the encoded command (nocommand=key);command_finishedno longer reports exit code/current directory.command_startedby reading the last history entry (fallback toBASH_COMMAND).How to test
I added debug logging to
initializeOSC133Support.tsin theterminal-widget-impl.tsfile.--log-level=debugflag to enableconsole.debugoutputExpected behavior:
Follow-ups
Breaking changes
Attribution
Contributed under the supervision of @JonasHelming as part of the TUM Bachelor Thesis project "Enhancing Terminal Usability in Modern IDEs through AI-Assisted Interaction".
Review checklist
nlsservice (for details, please see the Internationalization/Localization section in the Coding Guidelines)Reminder for reviewers