mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-29 16:20:29 -04:00
Refresh master with initial release/0.24 snapshot (#332)
* Initial port of release/0.24 source code * Fix additional headers * Fix a typo in launch.json
This commit is contained in:
@@ -9,63 +9,70 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IEditor } from 'vs/editor/common/editorCommon';
|
||||
import { IEditor as IBaseEditor, IEditorInput, ITextEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor';
|
||||
import { EditorInput, IEditorCloseEvent, IEditorRegistry, Extensions, toResource, IEditorGroup } from 'vs/workbench/common/editor';
|
||||
import { IEditor as IBaseEditor, IEditorInput, ITextEditorOptions, IResourceInput, ITextEditorSelection, Position as GroupPosition } from 'vs/platform/editor/common/editor';
|
||||
import { Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorGroup, IEditorInputFactoryRegistry, toResource } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { FileChangesEvent, IFileService, FileChangeType } from 'vs/platform/files/common/files';
|
||||
import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { once, debounceEvent } from 'vs/base/common/event';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
|
||||
import { getExcludes, ISearchConfiguration } from 'vs/platform/search/common/search';
|
||||
import { parse, IExpression } from 'vs/base/common/glob';
|
||||
import { IExpression } from 'vs/base/common/glob';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ResourceGlobMatcher } from 'vs/workbench/common/resources';
|
||||
import { IEditorRegistry, Extensions } from 'vs/workbench/browser/editor';
|
||||
|
||||
/**
|
||||
* Stores the selection & view state of an editor and allows to compare it to other selection states.
|
||||
*/
|
||||
export class EditorState {
|
||||
export class TextEditorState {
|
||||
|
||||
private static EDITOR_SELECTION_THRESHOLD = 5; // number of lines to move in editor to justify for new state
|
||||
private static EDITOR_SELECTION_THRESHOLD = 10; // number of lines to move in editor to justify for new state
|
||||
|
||||
private textEditorSelection: ITextEditorSelection;
|
||||
|
||||
constructor(private _editorInput: IEditorInput, private _selection: Selection) {
|
||||
this.textEditorSelection = Selection.isISelection(_selection) ? {
|
||||
startLineNumber: _selection.startLineNumber,
|
||||
startColumn: _selection.startColumn
|
||||
} : void 0;
|
||||
}
|
||||
|
||||
public get editorInput(): IEditorInput {
|
||||
return this._editorInput;
|
||||
}
|
||||
|
||||
public get selection(): Selection {
|
||||
return this._selection;
|
||||
public get selection(): ITextEditorSelection {
|
||||
return this.textEditorSelection;
|
||||
}
|
||||
|
||||
public justifiesNewPushState(other: EditorState, event?: ICursorPositionChangedEvent): boolean {
|
||||
public justifiesNewPushState(other: TextEditorState, event?: ICursorPositionChangedEvent): boolean {
|
||||
if (event && event.source === 'api') {
|
||||
return true; // always let API source win (e.g. "Go to definition" should add a history entry)
|
||||
}
|
||||
|
||||
if (!this._editorInput.matches(other._editorInput)) {
|
||||
return true; // push different editor inputs
|
||||
return true; // different editor inputs
|
||||
}
|
||||
|
||||
if (!Selection.isISelection(this._selection) || !Selection.isISelection(other._selection)) {
|
||||
return true; // unknown selections
|
||||
}
|
||||
|
||||
if (event && event.source === 'api') {
|
||||
return true; // always let API source win (e.g. "Go to definition" should add a history entry)
|
||||
}
|
||||
|
||||
const myLineNumber = Math.min(this._selection.selectionStartLineNumber, this._selection.positionLineNumber);
|
||||
const thisLineNumber = Math.min(this._selection.selectionStartLineNumber, this._selection.positionLineNumber);
|
||||
const otherLineNumber = Math.min(other._selection.selectionStartLineNumber, other._selection.positionLineNumber);
|
||||
|
||||
if (Math.abs(myLineNumber - otherLineNumber) < EditorState.EDITOR_SELECTION_THRESHOLD) {
|
||||
if (Math.abs(thisLineNumber - otherLineNumber) < TextEditorState.EDITOR_SELECTION_THRESHOLD) {
|
||||
return false; // ignore selection changes in the range of EditorState.EDITOR_SELECTION_THRESHOLD lines
|
||||
}
|
||||
|
||||
@@ -73,9 +80,14 @@ export class EditorState {
|
||||
}
|
||||
}
|
||||
|
||||
interface ISerializedFileHistoryEntry {
|
||||
resource?: string;
|
||||
resourceJSON: object;
|
||||
interface ISerializedEditorHistoryEntry {
|
||||
resourceJSON?: object;
|
||||
editorInputJSON?: { typeId: string; deserialized: string; };
|
||||
}
|
||||
|
||||
interface IEditorIdentifier {
|
||||
editor: IEditorInput;
|
||||
position: GroupPosition;
|
||||
}
|
||||
|
||||
export abstract class BaseHistoryService {
|
||||
@@ -83,6 +95,7 @@ export abstract class BaseHistoryService {
|
||||
protected toUnbind: IDisposable[];
|
||||
|
||||
private activeEditorListeners: IDisposable[];
|
||||
private lastActiveEditor: IEditorIdentifier;
|
||||
|
||||
constructor(
|
||||
protected editorGroupService: IEditorGroupService,
|
||||
@@ -96,25 +109,46 @@ export abstract class BaseHistoryService {
|
||||
}
|
||||
|
||||
private onEditorsChanged(): void {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
if (this.lastActiveEditor && this.matchesEditor(this.lastActiveEditor, activeEditor)) {
|
||||
return; // return if the active editor is still the same
|
||||
}
|
||||
|
||||
// Remember as last active editor (can be undefined if none opened)
|
||||
this.lastActiveEditor = activeEditor ? { editor: activeEditor.input, position: activeEditor.position } : void 0;
|
||||
|
||||
// Dispose old listeners
|
||||
dispose(this.activeEditorListeners);
|
||||
this.activeEditorListeners = [];
|
||||
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
|
||||
// Propagate to history
|
||||
this.handleActiveEditorChange(activeEditor);
|
||||
|
||||
// Apply listener for selection changes if this is a text editor
|
||||
const control = getCodeEditor(activeEditor);
|
||||
if (control) {
|
||||
this.activeEditorListeners.push(control.onDidChangeCursorPosition(event => {
|
||||
|
||||
// Debounce the event with a timeout of 0ms so that multiple calls to
|
||||
// editor.setSelection() are folded into one. We do not want to record
|
||||
// subsequent history navigations for such API calls.
|
||||
this.activeEditorListeners.push(debounceEvent(control.onDidChangeCursorPosition, (last, event) => event, 0)((event => {
|
||||
this.handleEditorSelectionChangeEvent(activeEditor, event);
|
||||
}));
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
private matchesEditor(identifier: IEditorIdentifier, editor?: IBaseEditor): boolean {
|
||||
if (!editor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (identifier.position !== editor.position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return identifier.editor.matches(editor.input);
|
||||
}
|
||||
|
||||
protected abstract handleExcludesChange(): void;
|
||||
|
||||
protected abstract handleEditorSelectionChangeEvent(editor?: IBaseEditor, event?: ICursorPositionChangedEvent): void;
|
||||
@@ -128,7 +162,7 @@ export abstract class BaseHistoryService {
|
||||
|
||||
interface IStackEntry {
|
||||
input: IEditorInput | IResourceInput;
|
||||
options?: ITextEditorOptions;
|
||||
selection?: ITextEditorSelection;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
@@ -145,12 +179,12 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
private static MAX_HISTORY_ITEMS = 200;
|
||||
private static MAX_STACK_ITEMS = 20;
|
||||
private static MAX_RECENTLY_CLOSED_EDITORS = 20;
|
||||
private static MERGE_EVENT_CHANGES_THRESHOLD = 100;
|
||||
|
||||
private stack: IStackEntry[];
|
||||
private index: number;
|
||||
private lastIndex: number;
|
||||
private navigatingInStack: boolean;
|
||||
private currentFileEditorState: EditorState;
|
||||
private currentTextEditorState: TextEditorState;
|
||||
|
||||
private history: (IEditorInput | IResourceInput)[];
|
||||
private recentlyClosedFiles: IRecentlyClosedFile[];
|
||||
@@ -172,23 +206,33 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
super(editorGroupService, editorService);
|
||||
|
||||
this.index = -1;
|
||||
this.lastIndex = -1;
|
||||
this.stack = [];
|
||||
this.recentlyClosedFiles = [];
|
||||
this.loaded = false;
|
||||
this.registry = Registry.as<IEditorRegistry>(Extensions.Editors);
|
||||
this.resourceFilter = instantiationService.createInstance(ResourceGlobMatcher, root => this.getExcludes(root), (expression: IExpression) => parse(expression));
|
||||
this.resourceFilter = instantiationService.createInstance(
|
||||
ResourceGlobMatcher,
|
||||
(root: URI) => this.getExcludes(root),
|
||||
(event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration('search.exclude')
|
||||
);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private setIndex(value: number): void {
|
||||
this.lastIndex = this.index;
|
||||
this.index = value;
|
||||
}
|
||||
|
||||
private getExcludes(root?: URI): IExpression {
|
||||
const scope = root ? { resource: root } : void 0;
|
||||
|
||||
return getExcludes(this.configurationService.getConfiguration<ISearchConfiguration>(void 0, scope));
|
||||
return getExcludes(this.configurationService.getConfiguration<ISearchConfiguration>(scope));
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.lifecycleService.onShutdown(reason => this.save()));
|
||||
this.toUnbind.push(this.lifecycleService.onShutdown(reason => this.saveHistory()));
|
||||
this.toUnbind.push(this.editorGroupService.onEditorOpenFail(editor => this.remove(editor)));
|
||||
this.toUnbind.push(this.editorGroupService.getStacksModel().onEditorClosed(event => this.onEditorClosed(event)));
|
||||
this.toUnbind.push(this.fileService.onFileChanges(e => this.onFileChanges(e)));
|
||||
@@ -205,12 +249,13 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
|
||||
// Track closing of pinned editor to support to reopen closed editors
|
||||
if (event.pinned) {
|
||||
const file = toResource(event.editor, { filter: 'file' }); // we only support files to reopen
|
||||
if (file) {
|
||||
const resource = event.editor ? event.editor.getResource() : void 0;
|
||||
const supportsReopen = resource && this.fileService.canHandleResource(resource); // we only support file'ish things to reopen
|
||||
if (supportsReopen) {
|
||||
|
||||
// Remove all inputs matching and add as last recently closed
|
||||
this.removeFromRecentlyClosedFiles(event.editor);
|
||||
this.recentlyClosedFiles.push({ resource: file, index: event.index });
|
||||
this.recentlyClosedFiles.push({ resource, index: event.index });
|
||||
|
||||
// Bounding
|
||||
if (this.recentlyClosedFiles.length > HistoryService.MAX_RECENTLY_CLOSED_EDITORS) {
|
||||
@@ -246,7 +291,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
}
|
||||
|
||||
private doForwardInEditors(): void {
|
||||
this.index++;
|
||||
this.setIndex(this.index + 1);
|
||||
this.navigate();
|
||||
}
|
||||
|
||||
@@ -260,8 +305,9 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
|
||||
const previousEntry = this.stack[currentIndex];
|
||||
if (!this.matches(currentEntry.input, previousEntry.input)) {
|
||||
this.index = currentIndex;
|
||||
this.setIndex(currentIndex);
|
||||
this.navigate(true /* across editors */);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -277,8 +323,17 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
}
|
||||
}
|
||||
|
||||
public last(): void {
|
||||
if (this.lastIndex === -1) {
|
||||
this.back();
|
||||
} else {
|
||||
this.setIndex(this.lastIndex);
|
||||
this.navigate();
|
||||
}
|
||||
}
|
||||
|
||||
private doBackInEditors(): void {
|
||||
this.index--;
|
||||
this.setIndex(this.index - 1);
|
||||
this.navigate();
|
||||
}
|
||||
|
||||
@@ -292,8 +347,9 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
|
||||
const previousEntry = this.stack[currentIndex];
|
||||
if (!this.matches(currentEntry.input, previousEntry.input)) {
|
||||
this.index = currentIndex;
|
||||
this.setIndex(currentIndex);
|
||||
this.navigate(true /* across editors */);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -303,6 +359,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
this.ensureHistoryLoaded();
|
||||
|
||||
this.index = -1;
|
||||
this.lastIndex = -1;
|
||||
this.stack.splice(0);
|
||||
this.history = [];
|
||||
this.recentlyClosedFiles = [];
|
||||
@@ -311,11 +368,15 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
private navigate(acrossEditors?: boolean): void {
|
||||
const entry = this.stack[this.index];
|
||||
|
||||
let options = entry.options;
|
||||
if (options && !acrossEditors /* ignore line/col options when going across editors */) {
|
||||
options.revealIfOpened = true;
|
||||
} else {
|
||||
options = { revealIfOpened: true };
|
||||
const options: ITextEditorOptions = {
|
||||
revealIfOpened: true // support to navigate across editor groups
|
||||
};
|
||||
|
||||
// Unless we navigate across editors, support selection and
|
||||
// minimize scrolling by setting revealInCenterIfOutsideViewport
|
||||
if (entry.selection && !acrossEditors) {
|
||||
options.selection = entry.selection;
|
||||
options.revealInCenterIfOutsideViewport = true;
|
||||
}
|
||||
|
||||
this.navigatingInStack = true;
|
||||
@@ -368,10 +429,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
// Remove this from the history unless the history input is a resource
|
||||
// that can easily be restored even when the input gets disposed
|
||||
if (historyInput instanceof EditorInput) {
|
||||
const onceDispose = once(historyInput.onDispose);
|
||||
onceDispose(() => {
|
||||
this.removeFromHistory(input);
|
||||
});
|
||||
once(historyInput.onDispose)(() => this.removeFromHistory(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,47 +473,50 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
|
||||
// treat editor changes that happen as part of stack navigation specially
|
||||
// we do not want to add a new stack entry as a matter of navigating the
|
||||
// stack but we need to keep our currentFileEditorState up to date with
|
||||
// stack but we need to keep our currentTextEditorState up to date with
|
||||
// the navigtion that occurs.
|
||||
if (this.navigatingInStack) {
|
||||
if (control && editor.input) {
|
||||
this.currentFileEditorState = new EditorState(editor.input, control.getSelection());
|
||||
this.currentTextEditorState = new TextEditorState(editor.input, control.getSelection());
|
||||
} else {
|
||||
this.currentFileEditorState = null; // we navigated to a non file editor
|
||||
this.currentTextEditorState = null; // we navigated to a non text editor
|
||||
}
|
||||
}
|
||||
|
||||
// normal navigation not part of history navigation
|
||||
else {
|
||||
|
||||
// navigation inside text editor
|
||||
if (control && editor.input) {
|
||||
this.handleTextEditorEvent(editor, control, event);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
// navigation to non-text editor
|
||||
else {
|
||||
this.currentTextEditorState = null; // at this time we have no active text editor view state
|
||||
|
||||
if (control && editor.input) {
|
||||
this.handleTextEditorEvent(editor, control, event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentFileEditorState = null; // at this time we have no active file editor view state
|
||||
|
||||
if (editor && editor.input) {
|
||||
this.handleNonTextEditorEvent(editor);
|
||||
if (editor && editor.input) {
|
||||
this.handleNonTextEditorEvent(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleTextEditorEvent(editor: IBaseEditor, editorControl: IEditor, event?: ICursorPositionChangedEvent): void {
|
||||
const stateCandidate = new EditorState(editor.input, editorControl.getSelection());
|
||||
if (!this.currentFileEditorState || this.currentFileEditorState.justifiesNewPushState(stateCandidate, event)) {
|
||||
this.currentFileEditorState = stateCandidate;
|
||||
const stateCandidate = new TextEditorState(editor.input, editorControl.getSelection());
|
||||
|
||||
let options: ITextEditorOptions;
|
||||
|
||||
const selection = editorControl.getSelection();
|
||||
if (selection) {
|
||||
options = {
|
||||
selection: { startLineNumber: selection.startLineNumber, startColumn: selection.startColumn }
|
||||
};
|
||||
}
|
||||
|
||||
this.add(editor.input, options, true /* from event */);
|
||||
// Add to stack if we dont have a current state or this new state justifies a push
|
||||
if (!this.currentTextEditorState || this.currentTextEditorState.justifiesNewPushState(stateCandidate, event)) {
|
||||
this.add(editor.input, stateCandidate.selection);
|
||||
}
|
||||
|
||||
// Otherwise we replace the current stack entry with this one
|
||||
else {
|
||||
this.replace(editor.input, stateCandidate.selection);
|
||||
}
|
||||
|
||||
// Update our current text editor state
|
||||
this.currentTextEditorState = stateCandidate;
|
||||
}
|
||||
|
||||
private handleNonTextEditorEvent(editor: IBaseEditor): void {
|
||||
@@ -464,39 +525,43 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
return; // do not push same editor input again
|
||||
}
|
||||
|
||||
this.add(editor.input, void 0, true /* from event */);
|
||||
this.add(editor.input);
|
||||
}
|
||||
|
||||
public add(input: IEditorInput, options?: ITextEditorOptions, fromEvent?: boolean): void {
|
||||
public add(input: IEditorInput, selection?: ITextEditorSelection): void {
|
||||
if (!this.navigatingInStack) {
|
||||
this.addToStack(input, options, fromEvent);
|
||||
this.addOrReplaceInStack(input, selection);
|
||||
}
|
||||
}
|
||||
|
||||
private addToStack(input: IEditorInput, options?: ITextEditorOptions, fromEvent?: boolean): void {
|
||||
private replace(input: IEditorInput, selection?: ITextEditorSelection): void {
|
||||
if (!this.navigatingInStack) {
|
||||
this.addOrReplaceInStack(input, selection, true /* force replace */);
|
||||
}
|
||||
}
|
||||
|
||||
private addOrReplaceInStack(input: IEditorInput, selection?: ITextEditorSelection, forceReplace?: boolean): void {
|
||||
|
||||
// Overwrite an entry in the stack if we have a matching input that comes
|
||||
// with editor options to indicate that this entry is more specific. Also
|
||||
// prevent entries that have the exact same options. Finally, Overwrite
|
||||
// entries if it came from an event and we detect that the change came in
|
||||
// very fast which indicates that it was not coming in from a user change
|
||||
// but rather rapid programmatic changes. We just take the last of the changes
|
||||
// to not cause too many entries on the stack.
|
||||
// entries if we detect that the change came in very fast which indicates
|
||||
// that it was not coming in from a user change but rather rapid programmatic
|
||||
// changes. We just take the last of the changes to not cause too many entries
|
||||
// on the stack.
|
||||
// We can also be instructed to force replace the last entry.
|
||||
let replace = false;
|
||||
if (this.stack[this.index]) {
|
||||
const currentEntry = this.stack[this.index];
|
||||
if (this.matches(input, currentEntry.input) && (this.sameOptions(currentEntry.options, options) || (fromEvent && Date.now() - currentEntry.timestamp < HistoryService.MERGE_EVENT_CHANGES_THRESHOLD))) {
|
||||
replace = true;
|
||||
const currentEntry = this.stack[this.index];
|
||||
if (currentEntry) {
|
||||
if (forceReplace) {
|
||||
replace = true; // replace if we are forced to
|
||||
} else if (this.matches(input, currentEntry.input) && this.sameSelection(currentEntry.selection, selection)) {
|
||||
replace = true; // replace if the input is the same as the current one and the selection as well
|
||||
}
|
||||
}
|
||||
|
||||
const stackInput = this.preferResourceInput(input);
|
||||
const entry = { input: stackInput, options, timestamp: fromEvent ? Date.now() : void 0 };
|
||||
|
||||
// If we are not at the end of history, we remove anything after
|
||||
if (this.stack.length > this.index + 1) {
|
||||
this.stack = this.stack.slice(0, this.index + 1);
|
||||
}
|
||||
const entry = { input: stackInput, selection, timestamp: Date.now() };
|
||||
|
||||
// Replace at current position
|
||||
if (replace) {
|
||||
@@ -505,63 +570,58 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
|
||||
// Add to stack at current position
|
||||
else {
|
||||
this.index++;
|
||||
this.stack.splice(this.index, 0, entry);
|
||||
|
||||
// If we are not at the end of history, we remove anything after
|
||||
if (this.stack.length > this.index + 1) {
|
||||
this.stack = this.stack.slice(0, this.index + 1);
|
||||
}
|
||||
|
||||
this.stack.splice(this.index + 1, 0, entry);
|
||||
|
||||
// Check for limit
|
||||
if (this.stack.length > HistoryService.MAX_STACK_ITEMS) {
|
||||
this.stack.shift(); // remove first and dispose
|
||||
if (this.index > 0) {
|
||||
this.index--;
|
||||
if (this.lastIndex >= 0) {
|
||||
this.lastIndex--;
|
||||
}
|
||||
} else {
|
||||
this.setIndex(this.index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove this from the stack unless the stack input is a resource
|
||||
// that can easily be restored even when the input gets disposed
|
||||
if (stackInput instanceof EditorInput) {
|
||||
const onceDispose = once(stackInput.onDispose);
|
||||
onceDispose(() => {
|
||||
this.removeFromStack(input);
|
||||
});
|
||||
once(stackInput.onDispose)(() => this.removeFromStack(input));
|
||||
}
|
||||
}
|
||||
|
||||
private preferResourceInput(input: IEditorInput): IEditorInput | IResourceInput {
|
||||
const file = toResource(input, { filter: 'file' });
|
||||
if (file) {
|
||||
return { resource: file };
|
||||
const resource = input ? input.getResource() : void 0;
|
||||
const preferResourceInput = resource && this.fileService.canHandleResource(resource); // file'ish things prefer resources
|
||||
if (preferResourceInput) {
|
||||
return { resource };
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private sameOptions(optionsA?: ITextEditorOptions, optionsB?: ITextEditorOptions): boolean {
|
||||
if (!optionsA && !optionsB) {
|
||||
private sameSelection(selectionA?: ITextEditorSelection, selectionB?: ITextEditorSelection): boolean {
|
||||
if (!selectionA && !selectionB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!optionsA && optionsB) || (optionsA && !optionsB)) {
|
||||
if ((!selectionA && selectionB) || (selectionA && !selectionB)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const s1 = optionsA.selection;
|
||||
const s2 = optionsB.selection;
|
||||
|
||||
if (!s1 && !s2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!s1 && s2) || (s1 && !s2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return s1.startLineNumber === s2.startLineNumber; // we consider the history entry same if we are on the same line
|
||||
return selectionA.startLineNumber === selectionB.startLineNumber; // we consider the history entry same if we are on the same line
|
||||
}
|
||||
|
||||
private removeFromStack(arg1: IEditorInput | IResourceInput | FileChangesEvent): void {
|
||||
this.stack = this.stack.filter(e => !this.matches(arg1, e.input));
|
||||
this.index = this.stack.length - 1; // reset index
|
||||
this.lastIndex = -1;
|
||||
}
|
||||
|
||||
private removeFromRecentlyClosedFiles(arg1: IEditorInput | IResourceInput | FileChangesEvent): void {
|
||||
@@ -625,9 +685,9 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
}
|
||||
|
||||
if (arg2 instanceof EditorInput) {
|
||||
const file = toResource(arg2, { filter: 'file' });
|
||||
const inputResource = arg2.getResource();
|
||||
|
||||
return file && file.toString() === resource.toString();
|
||||
return inputResource && this.fileService.canHandleResource(inputResource) && inputResource.toString() === resource.toString();
|
||||
}
|
||||
|
||||
const resourceInput = arg2 as IResourceInput;
|
||||
@@ -649,45 +709,92 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
private save(): void {
|
||||
private saveHistory(): void {
|
||||
if (!this.history) {
|
||||
return; // nothing to save because history was not used
|
||||
}
|
||||
|
||||
const entries: ISerializedFileHistoryEntry[] = this.history.map(input => {
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
|
||||
|
||||
const entries: ISerializedEditorHistoryEntry[] = this.history.map(input => {
|
||||
|
||||
// Editor input: try via factory
|
||||
if (input instanceof EditorInput) {
|
||||
return void 0; // only file resource inputs are serializable currently
|
||||
const factory = registry.getEditorInputFactory(input.getTypeId());
|
||||
if (factory) {
|
||||
const deserialized = factory.serialize(input);
|
||||
if (deserialized) {
|
||||
return { editorInputJSON: { typeId: input.getTypeId(), deserialized } } as ISerializedEditorHistoryEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { resourceJSON: (input as IResourceInput).resource.toJSON() };
|
||||
// File resource: via URI.toJSON()
|
||||
else {
|
||||
return { resourceJSON: (input as IResourceInput).resource.toJSON() } as ISerializedEditorHistoryEntry;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}).filter(serialized => !!serialized);
|
||||
|
||||
this.storageService.store(HistoryService.STORAGE_KEY, JSON.stringify(entries), StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
private loadHistory(): void {
|
||||
let entries: ISerializedFileHistoryEntry[] = [];
|
||||
let entries: ISerializedEditorHistoryEntry[] = [];
|
||||
|
||||
const entriesRaw = this.storageService.get(HistoryService.STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
if (entriesRaw) {
|
||||
entries = JSON.parse(entriesRaw);
|
||||
entries = JSON.parse(entriesRaw).filter(entry => !!entry);
|
||||
}
|
||||
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
|
||||
|
||||
this.history = entries.map(entry => {
|
||||
const serializedFileInput = entry as ISerializedFileHistoryEntry;
|
||||
if (serializedFileInput.resource || serializedFileInput.resourceJSON) {
|
||||
return { resource: !!serializedFileInput.resourceJSON ? URI.revive(serializedFileInput.resourceJSON) : URI.parse(serializedFileInput.resource) } as IResourceInput;
|
||||
const serializedEditorHistoryEntry = entry as ISerializedEditorHistoryEntry;
|
||||
|
||||
// File resource: via URI.revive()
|
||||
if (serializedEditorHistoryEntry.resourceJSON) {
|
||||
return { resource: URI.revive(serializedEditorHistoryEntry.resourceJSON) } as IResourceInput;
|
||||
}
|
||||
|
||||
// Editor input: via factory
|
||||
const { editorInputJSON } = serializedEditorHistoryEntry;
|
||||
if (editorInputJSON && editorInputJSON.deserialized) {
|
||||
const factory = registry.getEditorInputFactory(editorInputJSON.typeId);
|
||||
if (factory) {
|
||||
const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized);
|
||||
if (input) {
|
||||
once(input.onDispose)(() => this.removeFromHistory(input)); // remove from history once disposed
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}).filter(input => !!input);
|
||||
}
|
||||
|
||||
public getLastActiveWorkspaceRoot(): URI {
|
||||
if (!this.contextService.hasWorkspace()) {
|
||||
public getLastActiveWorkspaceRoot(schemeFilter?: string): URI {
|
||||
|
||||
// No Folder: return early
|
||||
const folders = this.contextService.getWorkspace().folders;
|
||||
if (folders.length === 0) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Single Folder: return early
|
||||
if (folders.length === 1) {
|
||||
const resource = folders[0].uri;
|
||||
if (!schemeFilter || resource.scheme === schemeFilter) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Multiple folders: find the last active one
|
||||
const history = this.getHistory();
|
||||
for (let i = 0; i < history.length; i++) {
|
||||
const input = history[i];
|
||||
@@ -696,13 +803,44 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic
|
||||
}
|
||||
|
||||
const resourceInput = input as IResourceInput;
|
||||
const resourceWorkspace = this.contextService.getRoot(resourceInput.resource);
|
||||
if (schemeFilter && resourceInput.resource.scheme !== schemeFilter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const resourceWorkspace = this.contextService.getWorkspaceFolder(resourceInput.resource);
|
||||
if (resourceWorkspace) {
|
||||
return resourceWorkspace;
|
||||
return resourceWorkspace.uri;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to first workspace
|
||||
return this.contextService.getWorkspace().roots[0];
|
||||
// fallback to first workspace matching scheme filter if any
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
const resource = folders[i].uri;
|
||||
if (!schemeFilter || resource.scheme === schemeFilter) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
public getLastActiveFile(): URI {
|
||||
const history = this.getHistory();
|
||||
for (let i = 0; i < history.length; i++) {
|
||||
let resource: URI;
|
||||
|
||||
const input = history[i];
|
||||
if (input instanceof EditorInput) {
|
||||
resource = toResource(input, { filter: 'file' });
|
||||
} else {
|
||||
resource = (input as IResourceInput).resource;
|
||||
}
|
||||
|
||||
if (resource && resource.scheme === 'file') {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorInput, ITextEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorInput, IResourceInput } from 'vs/platform/editor/common/editor';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export const IHistoryService = createDecorator<IHistoryService>('historyService');
|
||||
@@ -19,11 +19,6 @@ export interface IHistoryService {
|
||||
*/
|
||||
reopenLastClosedEditor(): void;
|
||||
|
||||
/**
|
||||
* Add an entry to the navigation stack of the history.
|
||||
*/
|
||||
add(input: IEditorInput, options?: ITextEditorOptions): void;
|
||||
|
||||
/**
|
||||
* Navigate forwards in history.
|
||||
*
|
||||
@@ -40,6 +35,11 @@ export interface IHistoryService {
|
||||
*/
|
||||
back(acrossEditors?: boolean): void;
|
||||
|
||||
/**
|
||||
* Navigate forward or backwards to previous entry in history.
|
||||
*/
|
||||
last(): void;
|
||||
|
||||
/**
|
||||
* Removes an entry from history.
|
||||
*/
|
||||
@@ -58,6 +58,13 @@ export interface IHistoryService {
|
||||
/**
|
||||
* Looking at the editor history, returns the workspace root of the last file that was
|
||||
* inside the workspace and part of the editor history.
|
||||
*
|
||||
* @param schemeFilter optional filter to restrict roots by scheme.
|
||||
*/
|
||||
getLastActiveWorkspaceRoot(): URI;
|
||||
getLastActiveWorkspaceRoot(schemeFilter?: string): URI;
|
||||
|
||||
/**
|
||||
* Looking at the editor history, returns the resource of the last file tht was opened.
|
||||
*/
|
||||
getLastActiveFile(): URI;
|
||||
}
|
||||
Reference in New Issue
Block a user