Skip to content

Conversation

@Fangoling
Copy link
Contributor

@Fangoling Fangoling commented Dec 10, 2025

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:

commandHistory: TerminalBlock[]

TerminalBlock {
    command: string;
    output: string;
}

To support this, the terminal now exposes two new events:

  • onTerminalCommandStart
  • onTerminalPromptShow

These events allow extensions to reliably detect when a prompt appears and when a command begins.

Additionally, this PR adds:

  • visual dividers between command blocks, and
  • a new user preference to toggle these dividers (disabled by default).

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:

  • When the last terminal command is longer than the fixed number of lines, not the full context can be covered
  • The model could focus on the wrong commands when more than one command is in the extracted output

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.

\e]133;prompt_started\a
<terminal prompt>
\e]133;prompt_finished\a
\e]133;command_started;<terminal command>\a
<terminal command>
<terminal output>
\e]133;prompt_started\a

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:

  • We provide stand-in versions of:
    • .zlogin, .zprofile, .zshenv, .zshrc
  • Each stand-in sources the original file afterward
  • Integration is activated via environment variables (e.g., ZDOTDIR)

For bash:

  • The integration script is passed via startup arguments
  • Only .bashrc requires sourcing

A new ShellIntegrationInjector:

  • Detects the shell
  • Applies the correct injection strategy
  • Falls back gracefully when unsupported shells are used

Package static resources

  • add a new entry to the Wepack Config generator that uses the CopyWebpackPlugin to copy the shell integration scripts into the lib folder of the packaged application

Task 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_started is emitted when the task begins execution
  • prompt_started is emitted when the task exits or is killed
    This 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 .bashrc and 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:

  • Adapted directory variable: JETBRAINS_INTELLIJ_ZSH_DIR -> THEIA_ZSH_DIR.
  • Removed IntelliJ session bootstrap (JEDITERM_SOURCE and _INTELLIJ_FORCE_SET_* / _INTELLIJ_FORCE_PREPEND_* handling, plus the one-time precmd_functions hook injection).
  • Removed legacy command block support handling.

zsh/command-block-support.zsh

  • Renamed functions/vars from __jetbrains_intellij_* to __theia_*.
  • Removed gating/early-returns for INTELLIJ_TERMINAL_COMMAND_BLOCKS_REWORKED and P9K_VERSION (integration always loads).
  • Switched OSC protocol payloads from JetBrains OSC 1341;... to Theia OSC 133;... and changed command_started format to send only the encoded command (no command=... key).
  • Dropped command_finished + current_directory reporting and removed alias reporting (aliases_received).

zsh/zdotdir/
.zlogin, .zprofile, .zshenv, .zshrc, source-original.zsh

  • Rebranded variables from JETBRAINS_INTELLIJ_* to THEIA_*.
  • Removed optional debug logging controlled by JETBRAINS_INTELLIJ_TERMINAL_DEBUG_LOG_LEVEL.

bash/bash-integration.bash

  • Rebranded internals from JetBrains/IntelliJ to Theia (JETBRAINS_INTELLIJ_BASH_DIR -> THEIA_BASH_DIR, __jetbrains_intellij_restore_posix_flag -> __theia_restore_posix_flag).
  • Removed IntelliJ-provided environment injection (_INTELLIJ_FORCE_SET_* / _INTELLIJ_FORCE_PREPEND_* processing), plus optional sourcing of JEDITERM_USER_RCFILE and JEDITERM_SOURCE (+ args).
  • Dropped keybinding setup for Ctrl-left/right word movement.
  • Dropped IntelliJ command-history hook (__INTELLIJ_COMMAND_HISTFILE__ + EXIT trap).
  • Only sources command-block-support.bash (no command-block-support-reworked.bash), without readability checks.

bash/command-block-support.bash

  • Rebranded functions/vars from __jetbrains_intellij_* to __theia_*.
  • Removed the feature gate (INTELLIJ_TERMINAL_COMMAND_BLOCKS_REWORKED) so the integration always loads.
  • Switched OSC protocol from JetBrains OSC 1341;... to Theia OSC 133;... and changed command_started payload to emit only the encoded command (no command= key); command_finished no longer reports exit code/current directory.
  • Improved command capture for command_started by reading the last history entry (fallback to BASH_COMMAND).

How to test

I added debug logging to initializeOSC133Support.ts in the terminal-widget-impl.ts file.

  1. Start the application with the --log-level=debug flag to enable console.debug output
  2. Open a terminal
  3. Open the developer tools console and enable:
  • verbose logging in chrome
  • debug logging in firefox
  1. Execute any command in the terminal.
  2. Inspect the browser developer console.

Expected behavior:

  • The full reconstructed command history of the current terminal is printed.
  • The executed command and its complete output appear as a well-formed command block.

Follow-ups

  • the powerlevel10k theme for zsh does not work in combination with this feature, this issue is know to the jetbrains intellij team

Breaking changes

  • This PR introduces breaking changes and requires careful review. If yes, the breaking changes section in the changelog has been updated.

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

Reminder for reviewers

@github-project-automation github-project-automation bot moved this to Waiting on reviewers in PR Backlog Dec 10, 2025
@Fangoling Fangoling marked this pull request as draft December 10, 2025 00:17
@Fangoling Fangoling force-pushed the terminal-register-osc-sequences branch from 432c0ed to 72f85f6 Compare December 10, 2025 00:32
@Fangoling Fangoling marked this pull request as ready for review December 10, 2025 16:08
Copy link
Contributor

@sgraband sgraband left a 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.

@github-project-automation github-project-automation bot moved this from Waiting on reviewers to Waiting on author in PR Backlog Dec 11, 2025
Copy link
Member

@sdirix sdirix left a 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.

@Fangoling Fangoling requested review from sdirix and sgraband December 12, 2025 23:35
Copy link
Contributor

@sgraband sgraband left a 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?

@Fangoling Fangoling requested a review from sgraband December 17, 2025 22:59
@sdirix
Copy link
Member

sdirix commented Jan 8, 2026

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>
@Fangoling Fangoling force-pushed the terminal-register-osc-sequences branch from 923e425 to 69cf24d Compare January 13, 2026 17:53
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>
@Fangoling
Copy link
Contributor Author

Fangoling commented Jan 14, 2026

I will review at the latest next week.

I added the preference for terminal command history that you requested:

  • add experimental preference setting for enabling terminal history
  • add experimental flag to terminal divider and state that it depends on terminal history being enabled
  • disable usage/injection of shell config when terminal history is disabled
  • disable command history in ai-terminal when terminal history is disabled
  • disable rendering of terminal dividers when terminal history is disabled
  • changing the preference does not impact already created terminals

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)

  • I am injecting the terminal sequences by extending the TaskTerminalProcess in packages/task/src/node/process/process-task-runner.ts.
  • Currently the injection of osc sequences into the output is not disabled when disabling the command history.

Copy link
Member

@sdirix sdirix left a comment

Choose a reason for hiding this comment

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

Thanks for the update!

Please check my comments.

I tested it on my Ubuntu machine, it did work for me with bash but it did not work for zsh:

Image

Comment on lines 194 to 200
if (this.getEnableCommandHistory()) {
const commandHistory = this.terminalWidget.commandHistory;
const lastCommandBlock = commandHistory.at(-1);
if (lastCommandBlock) {
return [lastCommandBlock.command, lastCommandBlock.output];
}
}
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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).

Comment on lines 32 to 34
// Inject command_started OSC when task begins
const encoded = Buffer.from(command).toString('hex');
this.ringBuffer.enq(`\x1b]133;command_started;${encoded}\x07`);
Copy link
Member

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?

Copy link
Contributor Author

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.

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>
@Fangoling
Copy link
Contributor Author

Fangoling commented Jan 16, 2026

Thanks for the update!

Please check my comments.

I tested it on my Ubuntu machine, it did work for me with bash but it did not work for zsh:

Image

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.

@sdirix
Copy link
Member

sdirix commented Jan 16, 2026

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>
@Fangoling Fangoling requested a review from sdirix January 20, 2026 00:32
Signed-off-by: Fangxing Liu <fx.liu@tum.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting on author

Development

Successfully merging this pull request may close these issues.

3 participants