mirror of
https://github.com/Microsoft/vscode
synced 2024-11-05 18:29:38 +00:00
repl: reuse HistoryNavigator
This commit is contained in:
parent
c3e3e532d7
commit
517c3c050e
3 changed files with 6 additions and 174 deletions
|
@ -1,117 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const MAX_HISTORY_ENTRIES = 50;
|
||||
|
||||
/**
|
||||
* The repl history has the following characteristics:
|
||||
* - the history is stored in local storage up to N items
|
||||
* - every time a expression is evaluated, it is being added to the history
|
||||
* - when starting to navigate in history, the current expression is remembered to be able to go back
|
||||
* - when navigating in history and making changes to any expression, these changes are remembered until a expression is evaluated
|
||||
* - the navigation state is not remembered so that the user always ends up at the end of the history stack when evaluating a expression
|
||||
*/
|
||||
export class ReplHistory {
|
||||
|
||||
private historyPointer: number;
|
||||
private currentExpressionStoredMarkers: boolean;
|
||||
private historyOverwrites: Map<string, string>;
|
||||
|
||||
constructor(private history: string[]) {
|
||||
this.historyPointer = this.history.length;
|
||||
this.currentExpressionStoredMarkers = false;
|
||||
this.historyOverwrites = new Map<string, string>();
|
||||
}
|
||||
|
||||
public next(): string {
|
||||
return this.navigate(false);
|
||||
}
|
||||
|
||||
public previous(): string {
|
||||
return this.navigate(true);
|
||||
}
|
||||
|
||||
private navigate(previous: boolean): string {
|
||||
// validate new pointer
|
||||
let newPointer = -1;
|
||||
if (previous && this.historyPointer > 0 && this.history.length > this.historyPointer - 1) {
|
||||
newPointer = this.historyPointer - 1;
|
||||
} else if (!previous && this.history.length > this.historyPointer + 1) {
|
||||
newPointer = this.historyPointer + 1;
|
||||
}
|
||||
|
||||
if (newPointer >= 0) {
|
||||
|
||||
// remember pointer for next navigation
|
||||
this.historyPointer = newPointer;
|
||||
|
||||
// check for overwrite
|
||||
if (this.historyOverwrites.has(newPointer.toString())) {
|
||||
return this.historyOverwrites.get(newPointer.toString());
|
||||
}
|
||||
|
||||
return this.history[newPointer];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public remember(expression: string, fromPrevious: boolean): void {
|
||||
let previousPointer: number;
|
||||
|
||||
// this method is called after the user has navigated in the history. Therefor we need to
|
||||
// restore the value of the pointer from the point when the user started the navigation.
|
||||
if (fromPrevious) {
|
||||
previousPointer = this.historyPointer + 1;
|
||||
} else {
|
||||
previousPointer = this.historyPointer - 1;
|
||||
}
|
||||
|
||||
// when the user starts to navigate in history, add the current expression to the history
|
||||
// once so that the user can always navigate back to it and does not loose its data.
|
||||
if (previousPointer === this.history.length && !this.currentExpressionStoredMarkers) {
|
||||
this.history.push(expression);
|
||||
this.currentExpressionStoredMarkers = true;
|
||||
}
|
||||
|
||||
// keep edits that are made to history items up until the user actually evaluates a expression
|
||||
else {
|
||||
this.historyOverwrites.set(previousPointer.toString(), expression);
|
||||
}
|
||||
}
|
||||
|
||||
public evaluated(expression: string): void {
|
||||
// clear current expression that was stored previously to support history navigation now on evaluate
|
||||
if (this.currentExpressionStoredMarkers) {
|
||||
this.history.pop();
|
||||
}
|
||||
|
||||
// keep in local history if expression provided and not equal to previous expression stored in history
|
||||
if (expression && (this.history.length === 0 || this.history[this.history.length - 1] !== expression)) {
|
||||
this.history.push(expression);
|
||||
}
|
||||
|
||||
// advance History Pointer to the end
|
||||
this.historyPointer = this.history.length;
|
||||
|
||||
// reset marker
|
||||
this.currentExpressionStoredMarkers = false;
|
||||
|
||||
// reset overwrites
|
||||
this.historyOverwrites.clear();
|
||||
}
|
||||
|
||||
public save(): string[] {
|
||||
// remove current expression from history since it was not evaluated
|
||||
if (this.currentExpressionStoredMarkers) {
|
||||
this.history.pop();
|
||||
}
|
||||
if (this.history.length > MAX_HISTORY_ENTRIES) {
|
||||
this.history = this.history.splice(this.history.length - MAX_HISTORY_ENTRIES, MAX_HISTORY_ENTRIES);
|
||||
}
|
||||
|
||||
return this.history;
|
||||
}
|
||||
}
|
|
@ -31,7 +31,6 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
|
|||
import { ReplExpressionsRenderer, ReplExpressionsController, ReplExpressionsDataSource, ReplExpressionsActionProvider, ReplExpressionsAccessibilityProvider } from 'vs/workbench/parts/debug/electron-browser/replViewer';
|
||||
import { SimpleDebugEditor } from 'vs/workbench/parts/debug/electron-browser/simpleDebugEditor';
|
||||
import { ClearReplAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { ReplHistory } from 'vs/workbench/parts/debug/common/replHistory';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
@ -44,6 +43,7 @@ import { OpenMode, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults
|
|||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_ON_FIRST_DEBUG_REPL_LINE, CONTEXT_IN_DEBUG_REPL, CONTEXT_ON_LAST_DEBUG_REPL_LINE } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { HistoryNavigator } from 'vs/base/common/history';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
|
@ -67,7 +67,7 @@ export class Repl extends Panel implements IPrivateReplService {
|
|||
|
||||
private static readonly HALF_WIDTH_TYPICAL = 'n';
|
||||
|
||||
private static HISTORY: ReplHistory;
|
||||
private history: HistoryNavigator<string>;
|
||||
private static readonly REFRESH_DELAY = 500; // delay in ms to refresh the repl for new elements to show
|
||||
private static readonly REPL_INPUT_INITIAL_HEIGHT = 19;
|
||||
private static readonly REPL_INPUT_MAX_HEIGHT = 170;
|
||||
|
@ -97,6 +97,7 @@ export class Repl extends Panel implements IPrivateReplService {
|
|||
super(REPL_ID, telemetryService, themeService);
|
||||
|
||||
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50);
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
|
@ -144,10 +145,6 @@ export class Repl extends Panel implements IPrivateReplService {
|
|||
controller
|
||||
}, replTreeOptions);
|
||||
|
||||
if (!Repl.HISTORY) {
|
||||
Repl.HISTORY = new ReplHistory(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')));
|
||||
}
|
||||
|
||||
return this.tree.setInput(this.debugService.getModel());
|
||||
}
|
||||
|
||||
|
@ -210,9 +207,8 @@ export class Repl extends Panel implements IPrivateReplService {
|
|||
}
|
||||
|
||||
public navigateHistory(previous: boolean): void {
|
||||
const historyInput = previous ? Repl.HISTORY.previous() : Repl.HISTORY.next();
|
||||
const historyInput = previous ? this.history.previous() : this.history.next();
|
||||
if (historyInput) {
|
||||
Repl.HISTORY.remember(this.replInput.getValue(), previous);
|
||||
this.replInput.setValue(historyInput);
|
||||
// always leave cursor at the end.
|
||||
this.replInput.setPosition({ lineNumber: 1, column: historyInput.length + 1 });
|
||||
|
@ -221,7 +217,7 @@ export class Repl extends Panel implements IPrivateReplService {
|
|||
|
||||
public acceptReplInput(): void {
|
||||
this.debugService.addReplExpression(this.replInput.getValue());
|
||||
Repl.HISTORY.evaluated(this.replInput.getValue());
|
||||
this.history.add(this.replInput.getValue());
|
||||
this.replInput.setValue('');
|
||||
// Trigger a layout to shrink a potential multi line input
|
||||
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
|
@ -286,7 +282,7 @@ export class Repl extends Panel implements IPrivateReplService {
|
|||
}
|
||||
|
||||
public shutdown(): void {
|
||||
const replHistory = Repl.HISTORY.save();
|
||||
const replHistory = this.history.getHistory();
|
||||
if (replHistory.length) {
|
||||
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE);
|
||||
} else {
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ReplHistory } from 'vs/workbench/parts/debug/common/replHistory';
|
||||
|
||||
suite('Debug - Repl History', () => {
|
||||
let history: ReplHistory;
|
||||
|
||||
setup(() => {
|
||||
history = new ReplHistory(['one', 'two', 'three', 'four', 'five']);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
history = null;
|
||||
});
|
||||
|
||||
test('previous and next', () => {
|
||||
assert.equal(history.previous(), 'five');
|
||||
assert.equal(history.previous(), 'four');
|
||||
assert.equal(history.previous(), 'three');
|
||||
assert.equal(history.previous(), 'two');
|
||||
assert.equal(history.previous(), 'one');
|
||||
assert.equal(history.previous(), null);
|
||||
assert.equal(history.next(), 'two');
|
||||
assert.equal(history.next(), 'three');
|
||||
assert.equal(history.next(), 'four');
|
||||
assert.equal(history.next(), 'five');
|
||||
});
|
||||
|
||||
test('evaluated and remember', () => {
|
||||
history.evaluated('six');
|
||||
assert.equal(history.previous(), 'six');
|
||||
assert.equal(history.previous(), 'five');
|
||||
assert.equal(history.next(), 'six');
|
||||
|
||||
history.remember('six++', true);
|
||||
assert.equal(history.next(), 'six++');
|
||||
assert.equal(history.previous(), 'six');
|
||||
|
||||
history.evaluated('seven');
|
||||
assert.equal(history.previous(), 'seven');
|
||||
assert.equal(history.previous(), 'six');
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue