repl: reuse HistoryNavigator

This commit is contained in:
isidor 2018-06-11 16:38:36 +02:00
parent c3e3e532d7
commit 517c3c050e
3 changed files with 6 additions and 174 deletions

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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');
});
});