terminal: allow excluding programs by name from typeahead

Fixes https://github.com/microsoft/vscode/issues/110109
This commit is contained in:
Connor Peet 2020-11-09 12:24:18 -08:00
parent b5f6a521e0
commit e300dfcdd2
No known key found for this signature in database
GPG key ID: CF8FD2EA0DBC61BD
4 changed files with 67 additions and 7 deletions

View file

@ -8,10 +8,11 @@ import { Color } from 'vs/base/common/color';
import { debounce } from 'vs/base/common/decorators';
import { Emitter } from 'vs/base/common/event';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { escapeRegExpCharacters } from 'vs/base/common/strings';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { XTermAttributes, XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
import { IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
import { DEFAULT_LOCAL_ECHO_EXCLUDE, IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
import type { IBuffer, IBufferCell, IDisposable, ITerminalAddon, Terminal } from 'xterm';
const ESC = '\x1b';
@ -1178,11 +1179,16 @@ class TypeAheadStyle implements IDisposable {
}
}
const compileExcludeRegexp = (programs = DEFAULT_LOCAL_ECHO_EXCLUDE) =>
new RegExp(`\\b(${programs.map(escapeRegExpCharacters).join('|')})\\b`, 'i');
export class TypeAheadAddon extends Disposable implements ITerminalAddon {
private typeaheadStyle?: TypeAheadStyle;
private typeaheadThreshold = this.config.config.localEchoLatencyThreshold;
private excludeProgramRe = compileExcludeRegexp(this.config.config.localEchoExcludePrograms);
protected lastRow?: { y: number; startingX: number };
private timeline?: PredictionTimeline;
protected timeline?: PredictionTimeline;
private terminalTitle = '';
public stats?: PredictionStats;
/**
@ -1206,6 +1212,10 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
timeline.setShowPredictions(this.typeaheadThreshold === 0);
this._register(terminal.onData(e => this.onUserData(e)));
this._register(terminal.onTitleChange(title => {
this.terminalTitle = title;
this.reevaluatePredictorState(stats, timeline);
}));
this._register(terminal.onResize(() => {
timeline.setShowPredictions(false);
timeline.clearCursor();
@ -1214,6 +1224,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
this._register(this.config.onConfigChanged(() => {
style.onUpdate(this.config.config.localEchoStyle);
this.typeaheadThreshold = this.config.config.localEchoLatencyThreshold;
this.excludeProgramRe = compileExcludeRegexp(this.config.config.localEchoExcludePrograms);
this.reevaluatePredictorState(stats, timeline);
}));
this._register(this.processManager.onBeforeProcessData(e => this.onBeforeProcessData(e)));
@ -1260,8 +1271,14 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
* terminal cursor is not updated, causes issues.
*/
@debounce(100)
private reevaluatePredictorState(stats: PredictionStats, timeline: PredictionTimeline) {
if (this.typeaheadThreshold < 0) {
protected reevaluatePredictorState(stats: PredictionStats, timeline: PredictionTimeline) {
this.reevaluatePredictorStateNow(stats, timeline);
}
protected reevaluatePredictorStateNow(stats: PredictionStats, timeline: PredictionTimeline) {
if (this.excludeProgramRe.test(this.terminalTitle)) {
timeline.setShowPredictions(false);
} else if (this.typeaheadThreshold < 0) {
timeline.setShowPredictions(false);
} else if (this.typeaheadThreshold === 0) {
timeline.setShowPredictions(true);

View file

@ -136,11 +136,14 @@ export interface ITerminalConfiguration {
unicodeVersion: '6' | '11';
experimentalLinkProvider: boolean;
localEchoLatencyThreshold: number;
localEchoExcludePrograms: ReadonlyArray<string>;
localEchoStyle: 'bold' | 'dim' | 'italic' | 'underlined' | 'inverted' | string;
serverSpawn: boolean;
enablePersistentSessions: boolean;
}
export const DEFAULT_LOCAL_ECHO_EXCLUDE: ReadonlyArray<string> = ['vim', 'vi', 'nano', 'tmux'];
export interface ITerminalConfigHelper {
config: ITerminalConfiguration;

View file

@ -6,7 +6,7 @@
import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
import { localize } from 'vs/nls';
import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT } from 'vs/workbench/contrib/terminal/common/terminal';
import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_LOCAL_ECHO_EXCLUDE } from 'vs/workbench/contrib/terminal/common/terminal';
import { isMacintosh, isWindows, Platform } from 'vs/base/common/platform';
export const terminalConfiguration: IConfigurationNode = {
@ -358,6 +358,15 @@ export const terminalConfiguration: IConfigurationNode = {
minimum: -1,
default: 30,
},
'terminal.integrated.localEchoExcludePrograms': {
description: localize('terminal.integrated.localEchoExcludePrograms', "Experimental: local echo will be disabled when any of these program names are found in the terminal title."),
type: 'array',
items: {
type: 'string',
uniqueItems: true
},
default: DEFAULT_LOCAL_ECHO_EXCLUDE,
},
'terminal.integrated.localEchoStyle': {
description: localize('terminal.integrated.localEchoStyle', "Experimental: terminal style of locally echoed text; either a font style or an RGB color."),
default: 'dim',

View file

@ -8,9 +8,10 @@ import { Terminal } from 'xterm';
import { SinonStub, stub, useFakeTimers } from 'sinon';
import { Emitter } from 'vs/base/common/event';
import { IPrediction, PredictionStats, TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon';
import { IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
import { DEFAULT_LOCAL_ECHO_EXCLUDE, IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { timeout } from 'vs/base/common/async';
const CSI = `\x1b[`;
@ -92,7 +93,8 @@ suite('Workbench - Terminal Typeahead', () => {
setup(() => {
config = upcastPartial<ITerminalConfiguration>({
localEchoStyle: 'italic',
localEchoLatencyThreshold: 0
localEchoLatencyThreshold: 0,
localEchoExcludePrograms: DEFAULT_LOCAL_ECHO_EXCLUDE,
});
publicLog = stub();
addon = new TestTypeAheadAddon(
@ -260,6 +262,24 @@ suite('Workbench - Terminal Typeahead', () => {
onBeforeProcessData.fire({ data: 'o' });
}
});
test('disables on title change', async () => {
const t = createMockTerminal({ lines: ['hello|'] });
addon.activate(t.terminal);
await timeout(1000);
addon.reevaluateNow();
assert.strictEqual(addon.isShowing, true, 'expected to show initially');
t.onTitleChange.fire('foo - VIM.exe');
addon.reevaluateNow();
assert.strictEqual(addon.isShowing, false, 'expected to hide when vim is open');
t.onTitleChange.fire('foo - git.exe');
addon.reevaluateNow();
assert.strictEqual(addon.isShowing, true, 'expected to show again after vim closed');
});
});
});
@ -267,6 +287,14 @@ class TestTypeAheadAddon extends TypeAheadAddon {
public unlockLeftNavigating() {
this.lastRow = { y: 1, startingX: 1 };
}
public reevaluateNow() {
this.reevaluatePredictorStateNow(this.stats!, this.timeline!);
}
public get isShowing() {
return !!this.timeline?.isShowingPredictions;
}
}
function upcastPartial<T>(v: Partial<T>): T {
@ -292,6 +320,7 @@ function createMockTerminal({ lines, cursorAttrs }: {
}) {
const written: string[] = [];
const cursor = { y: 1, x: 1 };
const onTitleChange = new Emitter<string>();
const onData = new Emitter<string>();
const csiEmitter = new Emitter<number[]>();
@ -315,11 +344,13 @@ function createMockTerminal({ lines, cursorAttrs }: {
clearWritten: () => written.splice(0, written.length),
onData: (s: string) => onData.fire(s),
csiEmitter,
onTitleChange,
terminal: {
cols: 80,
rows: 5,
onResize: new Emitter<void>().event,
onData: onData.event,
onTitleChange: onTitleChange.event,
parser: {
registerCsiHandler(_: unknown, callback: () => void) {
csiEmitter.event(callback);