Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { ACTIVE_GROUP_TYPE, AUX_WINDOW_GROUP_TYPE, SIDE_GROUP_TYPE } from 'vs/wo
import type { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetection/terminalCommand';
import type { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
import type { IMenu } from 'vs/platform/actions/common/actions';
import type { Barrier } from 'vs/base/common/async';

export const ITerminalService = createDecorator<ITerminalService>('terminalService');
export const ITerminalConfigurationService = createDecorator<ITerminalConfigurationService>('terminalConfigurationService');
Expand Down Expand Up @@ -1050,6 +1051,12 @@ export interface ITerminalInstance extends IBaseTerminalInstance {
* @returns Whether the context menu should be suppressed.
*/
handleMouseEvent(event: MouseEvent, contextMenu: IMenu): Promise<{ cancelContextMenu: boolean } | void>;

/**
* Pause input events until the provided barrier is resolved.
* @param barrier The barrier to wait for until input events can continue.
*/
pauseInputEvents(barrier: Barrier): void;
}

export const enum XtermTerminalConstants {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as dom from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { AutoOpenBarrier, Promises, disposableTimeout, timeout } from 'vs/base/common/async';
import { AutoOpenBarrier, Barrier, Promises, disposableTimeout, timeout } from 'vs/base/common/async';
import { Codicon } from 'vs/base/common/codicons';
import { debounce } from 'vs/base/common/decorators';
import { ErrorNoTelemetry, onUnexpectedError } from 'vs/base/common/errors';
Expand Down Expand Up @@ -198,6 +198,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _lineDataEventAddon: LineDataEventAddon | undefined;
private readonly _scopedContextKeyService: IContextKeyService;
private _resizeDebouncer?: TerminalResizeDebouncer;
private _pauseInputEventBarrier: Barrier | undefined;
pauseInputEvents(barrier: Barrier): void {
this._pauseInputEventBarrier = barrier;
}

readonly capabilities = this._register(new TerminalCapabilityStoreMultiplexer());
readonly statusList: ITerminalStatusList;
Expand Down Expand Up @@ -806,6 +810,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {

this._register(this._processManager.onProcessData(e => this._onProcessData(e)));
this._register(xterm.raw.onData(async data => {
await this._pauseInputEventBarrier?.wait();
await this._processManager.write(data);
this._onDidInputData.fire(data);
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import type { Terminal as RawXtermTerminal } from '@xterm/xterm';
import * as dom from 'vs/base/browser/dom';
import { AutoOpenBarrier } from 'vs/base/common/async';
import { Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
Expand Down Expand Up @@ -157,15 +158,28 @@ class TerminalSuggestContribution extends DisposableStore implements ITerminalCo
return;
}
if (this._terminalSuggestWidgetVisibleContextKey) {
this._addon.value = this._instantiationService.createInstance(SuggestAddon, TerminalSuggestContribution._cachedPwshCommands, this._instance.capabilities, this._terminalSuggestWidgetVisibleContextKey);
xterm.loadAddon(this._addon.value);
this._addon.value.setPanel(dom.findParentWithClass(xterm.element!, 'panel')!);
this._addon.value.setScreen(xterm.element!.querySelector('.xterm-screen')!);
this.add(this._instance.onDidBlur(() => this._addon.value?.hideSuggestWidget()));
this.add(this._addon.value.onAcceptedCompletion(async text => {
const addon = this._addon.value = this._instantiationService.createInstance(SuggestAddon, TerminalSuggestContribution._cachedPwshCommands, this._instance.capabilities, this._terminalSuggestWidgetVisibleContextKey);
xterm.loadAddon(addon);
addon.setPanel(dom.findParentWithClass(xterm.element!, 'panel')!);
addon.setScreen(xterm.element!.querySelector('.xterm-screen')!);
this.add(this._instance.onDidBlur(() => addon.hideSuggestWidget()));
this.add(addon.onAcceptedCompletion(async text => {
this._instance.focus();
this._instance.sendText(text, false);
}));

// If completions are requested, pause and queue input events until completions are
// received. This fixing some problems in PowerShell, particularly enter not executing
// when typing quickly and some characters being printed twice.
let barrier: AutoOpenBarrier | undefined;
this.add(addon.onDidRequestCompletions(() => {
barrier = new AutoOpenBarrier(2000);
this._instance.pauseInputEvents(barrier);
}));
this.add(addon.onDidReceiveCompletions(() => {
barrier?.open();
barrier = undefined;
}));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
readonly onBell = this._onBell.event;
private readonly _onAcceptedCompletion = this._register(new Emitter<string>());
readonly onAcceptedCompletion = this._onAcceptedCompletion.event;
private readonly _onDidRequestCompletions = this._register(new Emitter<void>());
readonly onDidRequestCompletions = this._onDidRequestCompletions.event;
private readonly _onDidReceiveCompletions = this._register(new Emitter<void>());
readonly onDidReceiveCompletions = this._onDidReceiveCompletions.event;

constructor(
private readonly _cachedPwshCommands: Set<SimpleCompletionItem>,
Expand Down Expand Up @@ -211,6 +215,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
// completions being requested again right after accepting a completion
if (this._lastUserDataTimestamp > this._lastAcceptedCompletionTimestamp) {
this._onAcceptedCompletion.fire(SuggestAddon.requestCompletionsSequence);
this._onDidRequestCompletions.fire();
}
}

Expand Down Expand Up @@ -329,6 +334,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
private _replacementLength: number = 0;

private _handleCompletionsSequence(terminal: Terminal, data: string, command: string, args: string[]): void {
this._onDidReceiveCompletions.fire();

// Nothing to handle if the terminal is not attached
if (!terminal.element || !this._enableWidget || !this._promptInputModel) {
return;
Expand Down