-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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
Terminal Providers #46192
Comments
As @Tyriar pointed out in mail this is something we discussed this week as well, especially in the use case of tasks. I would like to suggest a different form of API instead of a terminal provider mainly because it will be unclear if two extensions register such a terminal provider. (side note: I looked into a task execution provider for a comparable use case this milestone and couldn't find a nice working solution there either). I would like to see three different sets of API around terminals that extensions then can use to control how a terminal is functioning:
Combining these three API would allow to implement a |
These would be translated by automatically and passed through the read event in this case.
|
@dbaeumer The main reason for this feature request for us is to be able to "hijack" or "substitute" a terminal that other extensions would start without changing their code. They use |
@IlyaBiryukov but your provider is a one of solution. If a second extensions wants to do this it either can't or replaces your hijack terminal in a way that your provider is lost. The proposed API would allow for equal participation on the output / input stream. This allows for |
@dbaeumer My idea was to form a chain of providers, when they fire one-by-one, and and each pass |
@IlyaBiryukov make sense. |
@IlyaBiryukov this is what we've come up with so far, I'm interested in your thoughts: export namespace window {
// A Terminal owns a process and a renderer
export interface Terminal {
// This is unchanged
readonly name: string;
// processId not needs to also return undefined when there is no backing
// pid (TerminalRenderer)
readonly processId: Thenable<number | undefined>;
// Setting to undefined will reset to use the maximum available
dimensions: TerminalDimensions;
// Send text to process
sendText(data: string): void; // in
// Data from the process
onData: Event<string>; // out
// Fires when the panel area is resized, this DOES NOT fire when `dimensions` is set
onDidChangeDimensions: Event<TerminalDimensions>;
}
// Creates and returns an empty TerminalRenderer
export function createTerminalRenderer(name: string): TerminalRenderer;
// A TerminalRender does not own a process, it's similar to an output window
// but it understands ANSI
export interface TerminalRenderer {
// Extensions can set the name (what appears in the dropdown)
name: string;
// Setting to undefined will reset to use the maximum available
dimensions: TerminalDimensions;
// Write to xterm.js
write(data: string): void; // out
// key press, or sendText was triggered by an extension on the terminal
onData: Event<string>; // in
// Fires when the panel area is resized, this DOES NOT fire when `dimensions` is set
onDidChangeDimensions: Event<TerminalDimensions>;
}
export interface TerminalDimensions {
cols: number;
rows: number;
}
// Allows listening to terminal creation events, when a TerminalRenderer is
// created in this manner it uses Terminal as a facade
export function onDidOpenTerminal: Event<Terminal>;
export let terminals: Terminal[];
// Do not allow interception/cancellation of terminals, we do not want to
// encourage extensions to do this.
} An example extension: When a terminal is created it creates a another terminal which mirrors it and accepts input: let wasMirrored = false;
const linkedTerminals: {[terminal: Terminal]: TerminalRenderer} = {};
onDidCreateTerminal(terminal => {
if (wasMirrored) {
// Ignore events that came from mirrored terminals
wasMirrored = false;
} else {
wasMirrored = true;
linkedTerminals[terminal] = window.createTerminalRenderer();
setupListeners(terminal, linkedTerminals[terminal]);
}
});
function setupListeners(master: Terminal, slave: TerminalRenderer) {
// Send print instructions to slave
master.onData(data => slave.write(data));
// Redirect slave input to master
slave.onData(data => master.sendText(data));
} |
I updated inline with feedback from @jrieken:
|
Added |
@Tyriar, here is my feedback on the proposed API:
|
|
Could this help with #46696? Would it allow us to manipulate the |
@weinand FYI. |
@DanTup no you cannot manipulate a terminal's environment that was launched ( |
Understood. Though maybe this pattern would work there - if this was extended to have an onWillCreateTerminal that could modify the environment, it might fix that issue? (I don't want to derail this though, so if it sounds reasonable I could add comments to the other issue suggesting it?) |
@Tyriar Thanks for pushing this change. I tested it by running the |
@alpaix the extension command works for me on VS Code's build task: |
I tried running it on my Windows desktop. I have a project with the following task: {
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "npm",
"args": [
"run",
"compile"
],
"problemMatcher": "$tsc-watch",
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
}
]
} Then I invoked Terminal API: Attach data listener command and executed the task. No data have been passed to the listener. At the same time, it worked perfectly well when I attached a listener to a regular Powershell terminal. |
@alpaix you need to use |
The Attach command asks for a concrete terminal instance to get attached to. So it does find the task terminal instance and adds a listener to the right terminal. I tweaked the sample extension to attach the listener to every new terminal: // vscode.window.onDidOpenTerminal
if ('onDidOpenTerminal' in vscode.window) {
(<any>vscode.window).onDidOpenTerminal((terminal: vscode.Terminal) => {
vscode.window.showInformationMessage(`onDidOpenTerminal, name: ${terminal.name}`);
(<any>terminal).onData((data: string) => {
console.log('onData: ' + data);
});
});
} After this change, it started getting the data. Does the implementation expect listeners to get attached within a short period of time after the terminal instance is created? Based on my observation, a listener attached later simply won't get any data, |
@alpaix it should work later as well. When onData is called the extension host tells the renderer process to start sending data events: It should fire the data event until the terminal instance is disposed/exited. |
I think @alpaix is asking what happpens to data that is already present in the terminal. Will it be sent as well or will it be lost in terms of events. |
@alpaix yes this is the case. |
@IlyaBiryukov so this is done, check the proposed API file for the latest: I made a few tweaks on the initial design, namely:
Any feedback on this would be great 😃. I've added pretty extensive doc comments so they're worth a read before trying it out. |
@Tyriar Excellent news! I like the new API very much and look forward to playing with it. Will it be shipping in the next insiders update? |
@IlyaBiryukov I think it's already in the latest, if not it will be in tomorrow's. |
@IlyaBiryukov also the samples I was working against while developing are in this commit: microsoft/vscode-extension-samples@5f23d63 |
@Tyriar is the |
Issue Type: Feature Request
Enable extensions provide custom terminals to other extensions and tasks
Today extensions and tasks use vscode's API
vscode.window.createTerminal(terminalOptions)
to create a new terminal window, and some other APIs to render task output as terminal output.Our extension can change the way a terminal is started, and we want user to be able to apply this change to other terminals and tasks vscode may be running without changing their code.
The idea is to introduce a concept of Terminal Provider, an object that will be chained when something starts a new terminal:
When a provider is registered, it will be called when anyone calls
window.createTerminal(...)
. Theprovider
argument increateTerminal
is the previous chained provider, or internal vscode implementation that actually creates a terminal.Registering or un-registering a provider should not affect existing terminals.
The text was updated successfully, but these errors were encountered: