mirror of
https://github.com/Microsoft/vscode
synced 2024-08-26 20:39:37 +00:00
Merge pull request #143212 from microsoft/tyriar/141006
Support terminal command history persistence
This commit is contained in:
commit
72ef0f3977
|
@ -106,10 +106,11 @@ export const enum TerminalSettingId {
|
|||
ShellIntegrationShowWelcome = 'terminal.integrated.shellIntegration.showWelcome',
|
||||
ShellIntegrationCommandIcon = 'terminal.integrated.shellIntegration.commandIcon',
|
||||
ShellIntegrationCommandIconError = 'terminal.integrated.shellIntegration.commandIconError',
|
||||
ShellIntegrationCommandIconSkipped = 'terminal.integrated.shellIntegration.commandIconSkipped'
|
||||
ShellIntegrationCommandIconSkipped = 'terminal.integrated.shellIntegration.commandIconSkipped',
|
||||
ShellIntegrationCommandHistory = 'terminal.integrated.shellIntegration.history'
|
||||
}
|
||||
|
||||
export enum WindowsShellType {
|
||||
export const enum WindowsShellType {
|
||||
CommandPrompt = 'cmd',
|
||||
PowerShell = 'pwsh',
|
||||
Wsl = 'wsl',
|
||||
|
|
|
@ -51,6 +51,7 @@ import { AbstractVariableResolverService } from 'vs/workbench/services/configura
|
|||
import { ITerminalQuickPickItem } from 'vs/workbench/contrib/terminal/browser/terminalProfileQuickpick';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { getIconId, getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
|
||||
import { getCommandHistory } from 'vs/workbench/contrib/terminal/common/history';
|
||||
|
||||
export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500';
|
||||
export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs");
|
||||
|
@ -2073,6 +2074,21 @@ export function registerTerminalActions() {
|
|||
return getSelectedInstances(accessor)?.[0].toggleSizeToContentWidth();
|
||||
}
|
||||
});
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TerminalCommandId.ClearCommandHistory,
|
||||
title: { value: localize('workbench.action.terminal.clearCommandHistory', "Clear Command History"), original: 'Clear Command History' },
|
||||
f1: true,
|
||||
category,
|
||||
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor) {
|
||||
getCommandHistory(accessor).clear();
|
||||
}
|
||||
});
|
||||
|
||||
// Some commands depend on platform features
|
||||
if (BrowserFeatures.clipboard.writeText) {
|
||||
registerAction2(class extends Action2 {
|
||||
|
|
|
@ -68,6 +68,7 @@ import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xterm
|
|||
import { ITerminalCommand, TerminalCapability } from 'vs/workbench/contrib/terminal/common/capabilities/capabilities';
|
||||
import { TerminalCapabilityStoreMultiplexer } from 'vs/workbench/contrib/terminal/common/capabilities/terminalCapabilityStore';
|
||||
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { getCommandHistory } from 'vs/workbench/contrib/terminal/common/history';
|
||||
import { DEFAULT_COMMANDS_TO_SKIP_SHELL, INavigationMode, ITerminalBackend, ITerminalProcessManager, ITerminalProfileResolverService, ProcessState, ShellIntegrationExitCode, TerminalCommandId, TERMINAL_CREATION_COMMANDS, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
|
||||
import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings';
|
||||
|
@ -399,11 +400,17 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
|||
this._register(this.capabilities.onDidAddCapability(e => {
|
||||
this._logService.debug('terminalInstance added capability', e);
|
||||
if (e === TerminalCapability.CwdDetection) {
|
||||
this.capabilities.get(TerminalCapability.CwdDetection)?.onDidChangeCwd((e) => {
|
||||
this.capabilities.get(TerminalCapability.CwdDetection)?.onDidChangeCwd(e => {
|
||||
this._cwd = e;
|
||||
this._xtermOnKey?.dispose();
|
||||
this.refreshTabLabels(this.title, TitleEventSource.Config);
|
||||
});
|
||||
} else if (e === TerminalCapability.CommandDetection) {
|
||||
this.capabilities.get(TerminalCapability.CommandDetection)?.onCommandFinished(e => {
|
||||
if (e.command.trim().length > 0) {
|
||||
this._instantiationService.invokeFunction(getCommandHistory)?.add(e.command, { shellType: this._shellType });
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
this._register(this.capabilities.onDidRemoveCapability(e => this._logService.debug('terminalInstance removed capability', e)));
|
||||
|
@ -756,65 +763,104 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
|||
}
|
||||
|
||||
async runRecent(type: 'command' | 'cwd'): Promise<void> {
|
||||
const commands = this.capabilities.get(TerminalCapability.CommandDetection)?.commands;
|
||||
if (!commands || !this.xterm) {
|
||||
if (!this.xterm) {
|
||||
return;
|
||||
}
|
||||
type Item = IQuickPickItem & { command?: ITerminalCommand };
|
||||
const items: Item[] = [];
|
||||
let items: (Item | IQuickPickItem | IQuickPickSeparator)[] = [];
|
||||
const commandMap: Set<string> = new Set();
|
||||
|
||||
const removeFromCommandHistoryButton: IQuickInputButton = {
|
||||
iconClass: ThemeIcon.asClassName(Codicon.close),
|
||||
tooltip: nls.localize('removeCommand', "Remove from Command History")
|
||||
};
|
||||
|
||||
if (type === 'command') {
|
||||
for (const entry of commands) {
|
||||
// trim off any whitespace and/or line endings
|
||||
const label = entry.command.trim();
|
||||
if (label.length === 0) {
|
||||
continue;
|
||||
}
|
||||
let detail = '';
|
||||
if (entry.cwd) {
|
||||
detail += `cwd: ${entry.cwd} `;
|
||||
}
|
||||
if (entry.exitCode) {
|
||||
// Since you cannot get the last command's exit code on pwsh, just whether it failed
|
||||
// or not, -1 is treated specially as simply failed
|
||||
if (entry.exitCode === -1) {
|
||||
detail += 'failed';
|
||||
} else {
|
||||
detail += `exitCode: ${entry.exitCode}`;
|
||||
const commands = this.capabilities.get(TerminalCapability.CommandDetection)?.commands;
|
||||
// Current session history
|
||||
if (commands && commands.length > 0) {
|
||||
for (const entry of commands) {
|
||||
// trim off any whitespace and/or line endings
|
||||
const label = entry.command.trim();
|
||||
if (label.length === 0) {
|
||||
continue;
|
||||
}
|
||||
let description = fromNow(entry.timestamp, true);
|
||||
if (entry.cwd) {
|
||||
description += ` @ ${entry.cwd}`;
|
||||
}
|
||||
if (entry.exitCode) {
|
||||
// Since you cannot get the last command's exit code on pwsh, just whether it failed
|
||||
// or not, -1 is treated specially as simply failed
|
||||
if (entry.exitCode === -1) {
|
||||
description += ' failed';
|
||||
} else {
|
||||
description += ` exitCode: ${entry.exitCode}`;
|
||||
}
|
||||
}
|
||||
description = description.trim();
|
||||
const iconClass = ThemeIcon.asClassName(Codicon.output);
|
||||
const buttons: IQuickInputButton[] = [{
|
||||
iconClass,
|
||||
tooltip: nls.localize('viewCommandOutput', "View Command Output"),
|
||||
alwaysVisible: true
|
||||
}];
|
||||
// Merge consecutive commands
|
||||
const lastItem = items.length > 0 ? items[items.length - 1] : undefined;
|
||||
if (lastItem?.type !== 'separator' && lastItem?.label === label) {
|
||||
lastItem.id = entry.timestamp.toString();
|
||||
lastItem.description = description;
|
||||
continue;
|
||||
}
|
||||
items.push({
|
||||
label,
|
||||
description,
|
||||
id: entry.timestamp.toString(),
|
||||
command: entry,
|
||||
buttons: (!entry.endMarker?.isDisposed && !entry.marker?.isDisposed && (entry.endMarker!.line - entry.marker!.line > 0)) ? buttons : undefined
|
||||
});
|
||||
commandMap.add(label);
|
||||
}
|
||||
detail = detail.trim();
|
||||
const iconClass = ThemeIcon.asClassName(Codicon.output);
|
||||
const buttons: IQuickInputButton[] = [{
|
||||
iconClass,
|
||||
tooltip: nls.localize('viewCommandOutput', "View Command Output"),
|
||||
alwaysVisible: true
|
||||
}];
|
||||
// Merge consecutive commands
|
||||
if (items.length > 0 && items[items.length - 1].label === label) {
|
||||
items[items.length - 1].id = entry.timestamp.toString();
|
||||
items[items.length - 1].detail = detail;
|
||||
continue;
|
||||
items = items.reverse();
|
||||
items.unshift({ type: 'separator', label: 'current session' });
|
||||
}
|
||||
|
||||
// Gather previous session history
|
||||
const history = this._instantiationService.invokeFunction(getCommandHistory);
|
||||
const previousSessionItems: IQuickPickItem[] = [];
|
||||
for (const [label, info] of history.entries) {
|
||||
// Only add previous session item if it's not in this session
|
||||
if (!commandMap.has(label) && info.shellType === this.shellType) {
|
||||
previousSessionItems.push({
|
||||
label,
|
||||
buttons: [removeFromCommandHistoryButton]
|
||||
});
|
||||
}
|
||||
items.push({
|
||||
label,
|
||||
description: fromNow(entry.timestamp, true),
|
||||
detail,
|
||||
id: entry.timestamp.toString(),
|
||||
command: entry,
|
||||
buttons: (!entry.endMarker?.isDisposed && !entry.marker?.isDisposed && (entry.endMarker!.line - entry.marker!.line > 0)) ? buttons : undefined
|
||||
});
|
||||
}
|
||||
if (previousSessionItems.length > 0) {
|
||||
items.push(
|
||||
{ type: 'separator', label: 'previous sessions' },
|
||||
...previousSessionItems
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const cwds = this.capabilities.get(TerminalCapability.CwdDetection)?.cwds || [];
|
||||
for (const label of cwds) {
|
||||
items.push({ label });
|
||||
}
|
||||
items = items.reverse();
|
||||
}
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
const outputProvider = this._instantiationService.createInstance(TerminalOutputProvider);
|
||||
const quickPick = this._quickInputService.createQuickPick();
|
||||
quickPick.items = items.reverse();
|
||||
quickPick.items = items;
|
||||
return new Promise<void>(r => {
|
||||
quickPick.onDidTriggerItemButton(async e => {
|
||||
if (e.button === removeFromCommandHistoryButton) {
|
||||
this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label);
|
||||
}
|
||||
const selectedCommand = (e.item as Item).command;
|
||||
const output = selectedCommand?.getOutput();
|
||||
if (output && selectedCommand?.command) {
|
||||
|
|
174
src/vs/workbench/contrib/terminal/common/history.ts
Normal file
174
src/vs/workbench/contrib/terminal/common/history.ts
Normal file
|
@ -0,0 +1,174 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { TerminalSettingId, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
|
||||
/**
|
||||
* Tracks a list of generic entries.
|
||||
*/
|
||||
export interface ITerminalPersistedHistory<T> {
|
||||
/**
|
||||
* The persisted entries.
|
||||
*/
|
||||
readonly entries: IterableIterator<[string, T]>;
|
||||
/**
|
||||
* Adds an entry.
|
||||
*/
|
||||
add(key: string, value: T): void;
|
||||
/**
|
||||
* Removes an entry.
|
||||
*/
|
||||
remove(key: string): void;
|
||||
/**
|
||||
* Clears all entries.
|
||||
*/
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
interface ISerializedCache<T> {
|
||||
entries: { key: string; value: T }[];
|
||||
}
|
||||
|
||||
const enum Constants {
|
||||
DefaultHistoryLimit = 100
|
||||
}
|
||||
|
||||
const enum StorageKeys {
|
||||
Entries = 'terminal.history.entries',
|
||||
Timestamp = 'terminal.history.timestamp'
|
||||
}
|
||||
|
||||
let commandHistory: ITerminalPersistedHistory<{ shellType: TerminalShellType }> | undefined = undefined;
|
||||
export function getCommandHistory(accessor: ServicesAccessor): ITerminalPersistedHistory<{ shellType: TerminalShellType }> {
|
||||
if (!commandHistory) {
|
||||
commandHistory = accessor.get(IInstantiationService).createInstance(TerminalPersistedHistory, 'commands') as TerminalPersistedHistory<{ shellType: TerminalShellType }>;
|
||||
}
|
||||
return commandHistory;
|
||||
}
|
||||
|
||||
export class TerminalPersistedHistory<T> extends Disposable implements ITerminalPersistedHistory<T> {
|
||||
private readonly _entries: LRUCache<string, T>;
|
||||
private _timestamp: number = 0;
|
||||
private _isReady = false;
|
||||
private _isStale = true;
|
||||
|
||||
get entries(): IterableIterator<[string, T]> {
|
||||
this._ensureUpToDate();
|
||||
return this._entries.entries();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _storageDataKey: string,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IStorageService private readonly _storageService: IStorageService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Init cache
|
||||
this._entries = new LRUCache<string, T>(this._getHistoryLimit());
|
||||
|
||||
// Listen for config changes to set history limit
|
||||
this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationCommandHistory)) {
|
||||
this._entries.limit = this._getHistoryLimit();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to cache changes from other windows
|
||||
this._storageService.onDidChangeValue(e => {
|
||||
if (e.key === this._getTimestampStorageKey() && !this._isStale) {
|
||||
this._isStale = this._storageService.getNumber(this._getTimestampStorageKey(), StorageScope.GLOBAL, 0) !== this._timestamp;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
add(key: string, value: T) {
|
||||
this._ensureUpToDate();
|
||||
this._entries.set(key, value);
|
||||
this._saveState();
|
||||
}
|
||||
|
||||
remove(key: string) {
|
||||
this._ensureUpToDate();
|
||||
this._entries.delete(key);
|
||||
this._saveState();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._ensureUpToDate();
|
||||
this._entries.clear();
|
||||
this._saveState();
|
||||
}
|
||||
|
||||
private _ensureUpToDate() {
|
||||
// Initial load
|
||||
if (!this._isReady) {
|
||||
this._loadState();
|
||||
this._isReady = true;
|
||||
}
|
||||
|
||||
// React to stale cache caused by another window
|
||||
if (this._isStale) {
|
||||
// Since state is saved whenever the entries change, it's a safe assumption that no
|
||||
// merging of entries needs to happen, just loading the new state.
|
||||
this._entries.clear();
|
||||
this._loadState();
|
||||
this._isStale = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _loadState() {
|
||||
this._timestamp = this._storageService.getNumber(this._getTimestampStorageKey(), StorageScope.GLOBAL, 0);
|
||||
|
||||
// Load global entries plus
|
||||
const serialized = this._loadPersistedState();
|
||||
if (serialized) {
|
||||
for (const entry of serialized.entries) {
|
||||
this._entries.set(entry.key, entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _loadPersistedState(): ISerializedCache<T> | undefined {
|
||||
const raw = this._storageService.get(this._getEntriesStorageKey(), StorageScope.GLOBAL);
|
||||
if (raw === undefined || raw.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
let serialized: ISerializedCache<T> | undefined = undefined;
|
||||
try {
|
||||
serialized = JSON.parse(raw);
|
||||
} catch {
|
||||
// Invalid data
|
||||
return undefined;
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
private _saveState() {
|
||||
const serialized: ISerializedCache<T> = { entries: [] };
|
||||
this._entries.forEach((value, key) => serialized.entries.push({ key, value }));
|
||||
this._storageService.store(this._getEntriesStorageKey(), JSON.stringify(serialized), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
this._timestamp = Date.now();
|
||||
this._storageService.store(this._getTimestampStorageKey(), this._timestamp, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private _getHistoryLimit() {
|
||||
const historyLimit = this._configurationService.getValue(TerminalSettingId.ShellIntegrationCommandHistory);
|
||||
return typeof historyLimit === 'number' ? historyLimit : Constants.DefaultHistoryLimit;
|
||||
}
|
||||
|
||||
private _getTimestampStorageKey() {
|
||||
return `${StorageKeys.Timestamp}.${this._storageDataKey}`;
|
||||
}
|
||||
|
||||
private _getEntriesStorageKey() {
|
||||
return `${StorageKeys.Entries}.${this._storageDataKey}`;
|
||||
}
|
||||
}
|
|
@ -291,8 +291,8 @@ export interface ITerminalConfiguration {
|
|||
ignoreProcessNames: string[];
|
||||
autoReplies: { [key: string]: string };
|
||||
shellIntegration: {
|
||||
enabled: boolean
|
||||
}
|
||||
enabled: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const DEFAULT_LOCAL_ECHO_EXCLUDE: ReadonlyArray<string> = ['vim', 'vi', 'nano', 'tmux'];
|
||||
|
@ -559,6 +559,7 @@ export const enum TerminalCommandId {
|
|||
MoveToEditorInstance = 'workbench.action.terminal.moveToEditorInstance',
|
||||
MoveToTerminalPanel = 'workbench.action.terminal.moveToTerminalPanel',
|
||||
SetDimensions = 'workbench.action.terminal.setDimensions',
|
||||
ClearCommandHistory = 'workbench.action.terminal.clearCommandHistory',
|
||||
}
|
||||
|
||||
export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
|
||||
|
|
|
@ -543,6 +543,12 @@ const terminalConfiguration: IConfigurationNode = {
|
|||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
[TerminalSettingId.ShellIntegrationCommandHistory]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellIntegration.history', "Controls the number of recently used commands to keep in the terminal command history. Set to 0 to disable terminal command history."),
|
||||
type: 'number',
|
||||
default: 100
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
|
101
src/vs/workbench/contrib/terminal/test/common/history.test.ts
Normal file
101
src/vs/workbench/contrib/terminal/test/common/history.test.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { deepStrictEqual, strictEqual } from 'assert';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITerminalPersistedHistory, TerminalPersistedHistory } from 'vs/workbench/contrib/terminal/common/history';
|
||||
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
|
||||
function getConfig(limit: number) {
|
||||
return {
|
||||
terminal: {
|
||||
integrated: {
|
||||
shellIntegration: {
|
||||
history: limit
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
suite('TerminalPersistedHistory', () => {
|
||||
let history: ITerminalPersistedHistory<number>;
|
||||
let instantiationService: TestInstantiationService;
|
||||
let storageService: TestStorageService;
|
||||
let configurationService: TestConfigurationService;
|
||||
|
||||
setup(() => {
|
||||
configurationService = new TestConfigurationService(getConfig(5));
|
||||
storageService = new TestStorageService();
|
||||
instantiationService = new TestInstantiationService();
|
||||
instantiationService.set(IConfigurationService, configurationService);
|
||||
instantiationService.set(IStorageService, storageService);
|
||||
|
||||
history = instantiationService.createInstance(TerminalPersistedHistory, 'test');
|
||||
});
|
||||
|
||||
test('should support adding items to the cache and respect LRU', () => {
|
||||
history.add('foo', 1);
|
||||
deepStrictEqual(Array.from(history.entries), [
|
||||
['foo', 1]
|
||||
]);
|
||||
history.add('bar', 2);
|
||||
deepStrictEqual(Array.from(history.entries), [
|
||||
['foo', 1],
|
||||
['bar', 2]
|
||||
]);
|
||||
history.add('foo', 1);
|
||||
deepStrictEqual(Array.from(history.entries), [
|
||||
['bar', 2],
|
||||
['foo', 1]
|
||||
]);
|
||||
});
|
||||
|
||||
test('should support removing specific items', () => {
|
||||
history.add('1', 1);
|
||||
history.add('2', 2);
|
||||
history.add('3', 3);
|
||||
history.add('4', 4);
|
||||
history.add('5', 5);
|
||||
strictEqual(Array.from(history.entries).length, 5);
|
||||
history.add('6', 6);
|
||||
strictEqual(Array.from(history.entries).length, 5);
|
||||
});
|
||||
|
||||
test('should limit the number of entries based on config', () => {
|
||||
history.add('1', 1);
|
||||
history.add('2', 2);
|
||||
history.add('3', 3);
|
||||
history.add('4', 4);
|
||||
history.add('5', 5);
|
||||
strictEqual(Array.from(history.entries).length, 5);
|
||||
history.add('6', 6);
|
||||
strictEqual(Array.from(history.entries).length, 5);
|
||||
configurationService.setUserConfiguration('terminal', getConfig(2).terminal);
|
||||
configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any);
|
||||
strictEqual(Array.from(history.entries).length, 2);
|
||||
history.add('7', 7);
|
||||
strictEqual(Array.from(history.entries).length, 2);
|
||||
configurationService.setUserConfiguration('terminal', getConfig(3).terminal);
|
||||
configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: () => true } as any);
|
||||
strictEqual(Array.from(history.entries).length, 2);
|
||||
history.add('8', 8);
|
||||
strictEqual(Array.from(history.entries).length, 3);
|
||||
history.add('9', 9);
|
||||
strictEqual(Array.from(history.entries).length, 3);
|
||||
});
|
||||
|
||||
test('should reload from storage service after recreation', () => {
|
||||
history.add('1', 1);
|
||||
history.add('2', 2);
|
||||
history.add('3', 3);
|
||||
strictEqual(Array.from(history.entries).length, 3);
|
||||
const history2 = instantiationService.createInstance(TerminalPersistedHistory, 'test');
|
||||
strictEqual(Array.from(history2.entries).length, 3);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue