Skip to content

Commit

Permalink
Move TerminalShellIntegration stubs to main theia API
Browse files Browse the repository at this point in the history
fixes #14107

contributed on behalf of STMicroelectronics

Signed-off-by: Remi Schnekenburger <rschnekenburger@eclipsesource.com>
  • Loading branch information
rschnekenbu committed Sep 16, 2024
1 parent beabfba commit 378f6bf
Show file tree
Hide file tree
Showing 3 changed files with 363 additions and 330 deletions.
1 change: 1 addition & 0 deletions packages/plugin-ext/src/plugin/terminal-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ export class TerminalExtImpl implements theia.Terminal {
this.creationOptions = this.options;
}

/** @stubbed Terminal Shell Ingration */
shellIntegration: theia.TerminalShellIntegration | undefined = undefined;

sendText(text: string, shouldExecute: boolean = true): void {
Expand Down
363 changes: 362 additions & 1 deletion packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import './theia.proposed.resolvers';
import './theia.proposed.scmValidation';
import './theia.proposed.shareProvider';
import './theia.proposed.terminalQuickFixProvider';
import './theia.proposed.terminalShellIntegration';
import './theia.proposed.textSearchProvider';
import './theia.proposed.timeline';

Expand Down Expand Up @@ -3058,6 +3057,19 @@ export module '@theia/plugin' {
*/
readonly state: TerminalState;

/**
* An object that contains [shell integration](https://code.visualstudio.com/docs/terminal/shell-integration)-powered
* features for the terminal. This will always be `undefined` immediately after the terminal
* is created. Listen to {@link window.onDidChangeTerminalShellIntegration} to be notified
* when shell integration is activated for a terminal.
*
* Note that this object may remain undefined if shell integation never activates. For
* example Command Prompt does not support shell integration and a user's shell setup could
* conflict with the automatic shell integration activation.
* @stubbed
*/
readonly shellIntegration: TerminalShellIntegration | undefined;

/**
* Send text to the terminal.
* @param text - The text to send.
Expand Down Expand Up @@ -3101,6 +3113,333 @@ export module '@theia/plugin' {
readonly isInteractedWith: boolean;
}

/**
* [Shell integration](https://code.visualstudio.com/docs/terminal/shell-integration)-powered capabilities owned by a terminal.
* @stubbed
*/
export interface TerminalShellIntegration {
/**
* The current working directory of the terminal. This {@link Uri} may represent a file on
* another machine (eg. ssh into another machine). This requires the shell integration to
* support working directory reporting.
* @stubbed
*/
readonly cwd: Uri | undefined;

/**
* Execute a command, sending ^C as necessary to interrupt any running command if needed.
*
* @param commandLine The command line to execute, this is the exact text that will be sent
* to the terminal.
*
* @example
* // Execute a command in a terminal immediately after being created
* const myTerm = window.createTerminal();
* window.onDidChangeTerminalShellIntegration(async ({ terminal, shellIntegration }) => {
* if (terminal === myTerm) {
* const execution = shellIntegration.executeCommand('echo "Hello world"');
* window.onDidEndTerminalShellExecution(event => {
* if (event.execution === execution) {
* console.log(`Command exited with code ${event.exitCode}`);
* }
* }
* }));
* // Fallback to sendText if there is no shell integration within 3 seconds of launching
* setTimeout(() => {
* if (!myTerm.shellIntegration) {
* myTerm.sendText('echo "Hello world"');
* // Without shell integration, we can't know when the command has finished or what the
* // exit code was.
* }
* }, 3000);
*
* @example
* // Send command to terminal that has been alive for a while
* const commandLine = 'echo "Hello world"';
* if (term.shellIntegration) {
* const execution = shellIntegration.executeCommand({ commandLine });
* window.onDidEndTerminalShellExecution(event => {
* if (event.execution === execution) {
* console.log(`Command exited with code ${event.exitCode}`);
* }
* } else {
* term.sendText(commandLine);
* // Without shell integration, we can't know when the command has finished or what the
* // exit code was.
* }
* @stubbed
*/
executeCommand(commandLine: string): TerminalShellExecution;

/**
* Execute a command, sending ^C as necessary to interrupt any running command if needed.
*
* *Note* This is not guaranteed to work as [shell integration](https://code.visualstudio.com/docs/terminal/shell-integration)
* must be activated. Check whether {@link TerminalShellExecution.exitCode} is rejected to
* verify whether it was successful.
*
* @param command A command to run.
* @param args Arguments to launch the executable with which will be automatically escaped
* based on the executable type.
*
* @example
* // Execute a command in a terminal immediately after being created
* const myTerm = window.createTerminal();
* window.onDidActivateTerminalShellIntegration(async ({ terminal, shellIntegration }) => {
* if (terminal === myTerm) {
* const command = shellIntegration.executeCommand({
* command: 'echo',
* args: ['Hello world']
* });
* const code = await command.exitCode;
* console.log(`Command exited with code ${code}`);
* }
* }));
* // Fallback to sendText if there is no shell integration within 3 seconds of launching
* setTimeout(() => {
* if (!myTerm.shellIntegration) {
* myTerm.sendText('echo "Hello world"');
* // Without shell integration, we can't know when the command has finished or what the
* // exit code was.
* }
* }, 3000);
*
* @example
* // Send command to terminal that has been alive for a while
* const commandLine = 'echo "Hello world"';
* if (term.shellIntegration) {
* const command = term.shellIntegration.executeCommand({
* command: 'echo',
* args: ['Hello world']
* });
* const code = await command.exitCode;
* console.log(`Command exited with code ${code}`);
* } else {
* term.sendText(commandLine);
* // Without shell integration, we can't know when the command has finished or what the
* // exit code was.
* }
* @stubbed
*/
executeCommand(executable: string, args: string[]): TerminalShellExecution;
}

/**
* A command that was executed in a terminal.
* @stubbed
*/
export interface TerminalShellExecution {
/**
* The command line that was executed. The {@link TerminalShellExecutionCommandLineConfidence confidence}
* of this value depends on the specific shell's shell integration implementation. This
* value may become more accurate after {@link window.onDidEndTerminalShellExecution} is
* fired.
*
* @example
* // Log the details of the command line on start and end
* window.onDidStartTerminalShellExecution(event => {
* const commandLine = event.execution.commandLine;
* console.log(`Command started\n${summarizeCommandLine(commandLine)}`);
* });
* window.onDidEndTerminalShellExecution(event => {
* const commandLine = event.execution.commandLine;
* console.log(`Command ended\n${summarizeCommandLine(commandLine)}`);
* });
* function summarizeCommandLine(commandLine: TerminalShellExecutionCommandLine) {
* return [
* ` Command line: ${command.commandLine.value}`,
* ` Confidence: ${command.commandLine.confidence}`,
* ` Trusted: ${command.commandLine.isTrusted}
* ].join('\n');
* }
* @stubbed
*/
readonly commandLine: TerminalShellExecutionCommandLine;

/**
* The working directory that was reported by the shell when this command executed. This
* {@link Uri} may represent a file on another machine (eg. ssh into another machine). This
* requires the shell integration to support working directory reporting.
* @stubbed
*/
readonly cwd: Uri | undefined;

/**
* Creates a stream of raw data (including escape sequences) that is written to the
* terminal. This will only include data that was written after `read` was called for
* the first time, ie. you must call `read` immediately after the command is executed via
* {@link TerminalShellIntegration.executeCommand} or
* {@link window.onDidStartTerminalShellExecution} to not miss any data.
*
* @example
* // Log all data written to the terminal for a command
* const command = term.shellIntegration.executeCommand({ commandLine: 'echo "Hello world"' });
* const stream = command.read();
* for await (const data of stream) {
* console.log(data);
* }
* @stubbed
*/
read(): AsyncIterable<string>;
}

/**
* A command line that was executed in a terminal.
* @stubbed
*/
export interface TerminalShellExecutionCommandLine {
/**
* The full command line that was executed, including both the command and its arguments.
* @stubbed
*/
readonly value: string;

/**
* Whether the command line value came from a trusted source and is therefore safe to
* execute without user additional confirmation, such as a notification that asks "Do you
* want to execute (command)?". This verification is likely only needed if you are going to
* execute the command again.
*
* This is `true` only when the command line was reported explicitly by the shell
* integration script (ie. {@link TerminalShellExecutionCommandLineConfidence.High high confidence})
* and it used a nonce for verification.
* @stubbed
*/
readonly isTrusted: boolean;

/**
* The confidence of the command line value which is determined by how the value was
* obtained. This depends upon the implementation of the shell integration script.
* @stubbed
*/
readonly confidence: TerminalShellExecutionCommandLineConfidence;
}

/**
* The confidence of a {@link TerminalShellExecutionCommandLine} value.
*/
enum TerminalShellExecutionCommandLineConfidence {
/**
* The command line value confidence is low. This means that the value was read from the
* terminal buffer using markers reported by the shell integration script. Additionally one
* of the following conditions will be met:
*
* - The command started on the very left-most column which is unusual, or
* - The command is multi-line which is more difficult to accurately detect due to line
* continuation characters and right prompts.
* - Command line markers were not reported by the shell integration script.
*/
Low = 0,

/**
* The command line value confidence is medium. This means that the value was read from the
* terminal buffer using markers reported by the shell integration script. The command is
* single-line and does not start on the very left-most column (which is unusual).
*/
Medium = 1,

/**
* The command line value confidence is high. This means that the value was explicitly sent
* from the shell integration script or the command was executed via the
* {@link TerminalShellIntegration.executeCommand} API.
*/
High = 2
}

/**
* An event signalling that a terminal's shell integration has changed.
* @stubbed
*/
export interface TerminalShellIntegrationChangeEvent {
/**
* The terminal that shell integration has been activated in.
* @stubbed
*/
readonly terminal: Terminal;

/**
* The shell integration object.
* @stubbed
*/
readonly shellIntegration: TerminalShellIntegration;
}

/**
* An event signalling that an execution has started in a terminal.
* @stubbed
*/
export interface TerminalShellExecutionStartEvent {
/**
* The terminal that shell integration has been activated in.
* @stubbed
*/
readonly terminal: Terminal;

/**
* The shell integration object.
* @stubbed
*/
readonly shellIntegration: TerminalShellIntegration;

/**
* The terminal shell execution that has ended.
* @stubbed
*/
readonly execution: TerminalShellExecution;
}

/**
* An event signalling that an execution has ended in a terminal.
* @stubbed
*/
export interface TerminalShellExecutionEndEvent {
/**
* The terminal that shell integration has been activated in.
* @stubbed
*/
readonly terminal: Terminal;

/**
* The shell integration object.
* @stubbed
*/
readonly shellIntegration: TerminalShellIntegration;

/**
* The terminal shell execution that has ended.
* @stubbed
*/
readonly execution: TerminalShellExecution;

/**
* The exit code reported by the shell.
*
* Note that `undefined` means the shell either did not report an exit code (ie. the shell
* integration script is misbehaving) or the shell reported a command started before the command
* finished (eg. a sub-shell was opened). Generally this should not happen, depending on the use
* case, it may be best to treat this as a failure.
*
* @example
* const execution = shellIntegration.executeCommand({
* command: 'echo',
* args: ['Hello world']
* });
* window.onDidEndTerminalShellExecution(event => {
* if (event.execution === execution) {
* if (event.exitCode === undefined) {
* console.log('Command finished but exit code is unknown');
* } else if (event.exitCode === 0) {
* console.log('Command succeeded');
* } else {
* console.log('Command failed');
* }
* }
* });
* @stubbed
*/
readonly exitCode: number | undefined;
}

/**
* Options to create terminal widget.
*/
Expand Down Expand Up @@ -5500,6 +5839,28 @@ export module '@theia/plugin' {
*/
export const onDidChangeTerminalState: Event<Terminal>;

/**
* Fires when shell integration activates or one of its properties changes in a terminal.
* @stubbed
*/
export const onDidChangeTerminalShellIntegration: Event<TerminalShellIntegrationChangeEvent>;

/**
* This will be fired when a terminal command is started. This event will fire only when
* [shell integration](https://code.visualstudio.com/docs/terminal/shell-integration) is
* activated for the terminal.
* @stubbed
*/
export const onDidStartTerminalShellExecution: Event<TerminalShellExecutionStartEvent>;

/**
* This will be fired when a terminal command is ended. This event will fire only when
* [shell integration](https://code.visualstudio.com/docs/terminal/shell-integration) is
* activated for the terminal.
* @stubbed
*/
export const onDidEndTerminalShellExecution: Event<TerminalShellExecutionEndEvent>;

/**
* Create new terminal with predefined options.
* @param - terminal options.
Expand Down
Loading

0 comments on commit 378f6bf

Please sign in to comment.