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:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

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

View File

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