| /*--------------------------------------------------------- |
| * Copyright 2025 The Go Authors. All rights reserved. |
| * Licensed under the MIT License. See LICENSE in the project root for license information. |
| *--------------------------------------------------------*/ |
| import vscode = require('vscode'); |
| |
| import { ProgressToken } from 'vscode-languageclient'; |
| |
| // ActiveProgressTerminals maps progress tokens to their corresponding terminals. |
| // Entries are added when terminals are created for workdone progress and |
| // deleted when closed by the user, which is interpreted as the user discarding |
| // any further information. |
| // There is no guarantee a terminal will remain available for the entire |
| // duration of a workdone progress notification. |
| // Logs can be appended to the terminal even after the workdone progress |
| // notification finishes, allowing responses from requests extending |
| // WorkDoneProgressOptions to be displayed in the same terminal. |
| export const ActiveProgressTerminals = new Map<ProgressToken, IProgressTerminal>(); |
| |
| export interface IProgressTerminal { |
| appendLine: (str: string) => void; |
| show: (preserveFocus?: boolean) => void; |
| exit: () => void; |
| } |
| export class ProgressTerminal implements IProgressTerminal { |
| private progressToken?: ProgressToken; |
| private term: vscode.Terminal; |
| private writeEmitter = new vscode.EventEmitter<string>(); |
| |
| // Buffer messages emitted before vscode is ready. VSC calls pty.open when it is ready. |
| private ptyReady = false; |
| private buf: string[] = []; |
| |
| // Constructor function to stub during test. |
| static Open(name = 'progress', token?: ProgressToken): IProgressTerminal { |
| return new ProgressTerminal(name, token); |
| } |
| |
| // ProgressTerminal created with token will be managed by map |
| // ActiveProgressTerminals. |
| private constructor(name: string, token?: ProgressToken) { |
| const pty: vscode.Pseudoterminal = { |
| onDidWrite: this.writeEmitter.event, |
| handleInput: () => this.exit(), |
| open: () => { |
| this.ptyReady = true; |
| this.buf.forEach((l) => this.writeEmitter.fire(l)); |
| this.buf = []; |
| }, |
| close: () => { |
| if (this.progressToken !== undefined) { |
| ActiveProgressTerminals.delete(this.progressToken); |
| } |
| } |
| }; |
| this.term = vscode.window.createTerminal({ name: name, pty }); // TODO: iconPath |
| if (token !== undefined) { |
| this.progressToken = token; |
| ActiveProgressTerminals.set(this.progressToken, this); |
| } |
| } |
| |
| appendLine(str: string) { |
| if (!str.endsWith('\n')) { |
| str += '\n'; |
| } |
| str = str.replace(/\n/g, '\n\r'); // replaceAll('\n', '\n\r'). |
| if (!this.ptyReady) { |
| this.buf.push(str); // print when `open` is called. |
| } else { |
| this.writeEmitter.fire(str); |
| } |
| } |
| |
| show(preserveFocus?: boolean) { |
| this.term.show(preserveFocus); |
| } |
| |
| exit() {} |
| } |