提交 43abcc20 编写于 作者: D Dirk Baeumer

Fixes #28379: Run Task should be sorted using MRU list

上级 17773665
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
interface Item<K, V> {
previous: Item<K, V> | undefined;
next: Item<K, V> | undefined;
key: K;
value: V;
}
export namespace Touch {
export const None: 0 = 0;
export const First: 1 = 1;
export const Last: 2 = 2;
}
export type Touch = 0 | 1 | 2;
export class LinkedMap<K, V> {
private _map: Map<K, Item<K, V>>;
private _head: Item<K, V> | undefined;
private _tail: Item<K, V> | undefined;
private _size: number;
constructor() {
this._map = new Map<K, Item<K, V>>();
this._head = undefined;
this._tail = undefined;
this._size = 0;
}
public clear(): void {
this._map.clear();
this._head = undefined;
this._tail = undefined;
this._size = 0;
}
public isEmpty(): boolean {
return !this._head && !this._tail;
}
public get size(): number {
return this._size;
}
public has(key: K): boolean {
return this._map.has(key);
}
public get(key: K): V | undefined {
const item = this._map.get(key);
if (!item) {
return undefined;
}
return item.value;
}
public set(key: K, value: V, touch: Touch = Touch.None): void {
let item = this._map.get(key);
if (item) {
item.value = value;
if (touch !== Touch.None) {
this.touch(item, touch);
}
} else {
item = { key, value, next: undefined, previous: undefined };
switch (touch) {
case Touch.None:
this.addItemLast(item);
break;
case Touch.First:
this.addItemFirst(item);
break;
case Touch.Last:
this.addItemLast(item);
break;
default:
this.addItemLast(item);
break;
}
this._map.set(key, item);
this._size++;
}
}
public delete(key: K): boolean {
const item = this._map.get(key);
if (!item) {
return false;
}
this._map.delete(key);
this.removeItem(item);
this._size--;
return true;
}
public shift(): V | undefined {
if (!this._head && !this._tail) {
return undefined;
}
if (!this._head || !this._tail) {
throw new Error('Invalid list');
}
const item = this._head;
this._map.delete(item.key);
this.removeItem(item);
this._size--;
return item.value;
}
public forEach(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
let current = this._head;
while (current) {
if (thisArg) {
callbackfn.bind(thisArg)(current.value, current.key, this);
} else {
callbackfn(current.value, current.key, this);
}
current = current.next;
}
}
public forEachReverse(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
let current = this._tail;
while (current) {
if (thisArg) {
callbackfn.bind(thisArg)(current.value, current.key, this);
} else {
callbackfn(current.value, current.key, this);
}
current = current.previous;
}
}
public values(): V[] {
let result: V[] = [];
let current = this._head;
while (current) {
result.push(current.value);
current = current.next;
}
return result;
}
public keys(): K[] {
let result: K[] = [];
let current = this._head;
while (current) {
result.push(current.key);
current = current.next;
}
return result;
}
/* VS Code / Monaco editor runs on es5 which has no Symbol.iterator
public keys(): IterableIterator<K> {
let current = this._head;
let iterator: IterableIterator<K> = {
[Symbol.iterator]() {
return iterator;
},
next():IteratorResult<K> {
if (current) {
let result = { value: current.key, done: false };
current = current.next;
return result;
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
public values(): IterableIterator<V> {
let current = this._head;
let iterator: IterableIterator<V> = {
[Symbol.iterator]() {
return iterator;
},
next():IteratorResult<V> {
if (current) {
let result = { value: current.value, done: false };
current = current.next;
return result;
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
*/
private addItemFirst(item: Item<K, V>): void {
// First time Insert
if (!this._head && !this._tail) {
this._tail = item;
} else if (!this._head) {
throw new Error('Invalid list');
} else {
item.next = this._head;
this._head.previous = item;
}
this._head = item;
}
private addItemLast(item: Item<K, V>): void {
// First time Insert
if (!this._head && !this._tail) {
this._head = item;
} else if (!this._tail) {
throw new Error('Invalid list');
} else {
item.previous = this._tail;
this._tail.next = item;
}
this._tail = item;
}
private removeItem(item: Item<K, V>): void {
if (item === this._head && item === this._tail) {
this._head = undefined;
this._tail = undefined;
}
else if (item === this._head) {
this._head = item.next;
}
else if (item === this._tail) {
this._tail = item.previous;
}
else {
const next = item.next;
const previous = item.previous;
if (!next || !previous) {
throw new Error('Invalid list');
}
next.previous = previous;
previous.next = next;
}
}
private touch(item: Item<K, V>, touch: Touch): void {
if (!this._head || !this._tail) {
throw new Error('Invalid list');
}
if ((touch !== Touch.First && touch !== Touch.Last)) {
return;
}
if (touch === Touch.First) {
if (item === this._head) {
return;
}
const next = item.next;
const previous = item.previous;
// Unlink the item
if (item === this._tail) {
// previous must be defined since item was not head but is tail
// So there are more than on item in the map
previous!.next = undefined;
this._tail = previous;
}
else {
// Both next and previous are not undefined since item was neither head nor tail.
next!.previous = previous;
previous!.next = next;
}
// Insert the node at head
item.previous = undefined;
item.next = this._head;
this._head.previous = item;
this._head = item;
} else if (touch === Touch.Last) {
if (item === this._tail) {
return;
}
const next = item.next;
const previous = item.previous;
// Unlink the item.
if (item === this._head) {
// next must be defined since item was not tail but is head
// So there are more than on item in the map
next!.previous = undefined;
this._head = next;
} else {
// Both next and previous are not undefined since item was neither head nor tail.
next!.previous = previous;
previous!.next = next;
}
item.next = undefined;
item.previous = this._tail;
this._tail.next = item;
this._tail = item;
}
}
}
\ No newline at end of file
......@@ -8,6 +8,8 @@ import nls = require('vs/nls');
import Filters = require('vs/base/common/filters');
import { TPromise } from 'vs/base/common/winjs.base';
import { Action, IAction } from 'vs/base/common/actions';
import { IStringDictionary } from 'vs/base/common/collections';
import Quickopen = require('vs/workbench/browser/quickopen');
import QuickOpen = require('vs/base/parts/quickopen/common/quickOpen');
import Model = require('vs/base/parts/quickopen/browser/quickOpenModel');
......@@ -49,7 +51,7 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler {
constructor(
protected quickOpenService: IQuickOpenService,
protected taskService: ITaskService,
protected taskService: ITaskService
) {
super();
......@@ -71,39 +73,39 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler {
if (tasks.length === 0) {
return new Model.QuickOpenModel(entries);
}
tasks = tasks.sort((a, b) => {
let aKind = a._source.kind;
let bKind = b._source.kind;
if (aKind === bKind) {
if (aKind === TaskSourceKind.Extension) {
let compare = a._source.label.localeCompare(b._source.label);
if (compare !== 0) {
return compare;
}
}
return a._label.localeCompare(b._label);
}
if (aKind === TaskSourceKind.Workspace) {
return -1;
} else {
return +1;
let recentlyUsedTasks = this.taskService.getRecentlyUsedTasks();
let recent: Task[] = [];
let others: Task[] = [];
let taskMap: IStringDictionary<Task> = Object.create(null);
tasks.forEach(task => taskMap[task.identifier] = task);
recentlyUsedTasks.keys().forEach(key => {
let task = taskMap[key];
if (task) {
recent.push(task);
}
});
let firstWorkspace: boolean = true;
let firstExtension: boolean = true;
let hadWorkspace: boolean = false;
for (let task of tasks) {
if (!recentlyUsedTasks.has(task.identifier)) {
others.push(task);
}
}
others = others.sort((a, b) => a._source.label.localeCompare(b._source.label));
let sortedTasks = recent.concat(others);
let recentlyUsed: boolean = recentlyUsedTasks.has(tasks[0].identifier);
let otherTasks: boolean = !recentlyUsedTasks.has(tasks[tasks.length - 1].identifier);
let hasRecentlyUsed: boolean = false;
for (let task of sortedTasks) {
let highlights = Filters.matchesContiguousSubString(input, task._label);
if (!highlights) {
continue;
}
if (task._source.kind === TaskSourceKind.Workspace && firstWorkspace) {
firstWorkspace = false;
hadWorkspace = true;
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('configured', 'Configured Tasks'), false));
} else if (task._source.kind === TaskSourceKind.Extension && firstExtension) {
firstExtension = false;
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('detected', 'Detected Tasks'), hadWorkspace));
if (recentlyUsed) {
recentlyUsed = false;
hasRecentlyUsed = true;
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('recentlyUsed', 'recently used'), false));
} else if (!recentlyUsedTasks.has(task.identifier) && otherTasks) {
otherTasks = false;
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('other tasks', 'other tasks'), hasRecentlyUsed));
} else {
entries.push(this.createEntry(this.taskService, task, highlights));
}
......
......@@ -8,6 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { IEventEmitter } from 'vs/base/common/eventEmitter';
import { TerminateResponse } from 'vs/base/common/processes';
import { LinkedMap } from 'vs/base/common/linkedMap';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Task, TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskSummary, TaskEvent, TaskType } from 'vs/workbench/parts/tasks/common/taskSystem';
......@@ -43,6 +44,7 @@ export interface ITaskService extends IEventEmitter {
terminateAll(): TPromise<TerminateResponse>;
tasks(): TPromise<Task[]>;
getTasksForGroup(group: string): TPromise<Task[]>;
getRecentlyUsedTasks(): LinkedMap<string, string>;
customize(task: Task, openConfig?: boolean): TPromise<void>;
......
......@@ -29,6 +29,7 @@ import { TerminateResponse, TerminateResponseCode } from 'vs/base/common/process
import * as strings from 'vs/base/common/strings';
import { ValidationStatus, ValidationState } from 'vs/base/common/parsers';
import * as UUID from 'vs/base/common/uuid';
import { LinkedMap, Touch } from 'vs/base/common/linkedMap';
import { Registry } from 'vs/platform/platform';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
......@@ -45,7 +46,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ProblemMatcherRegistry } from 'vs/platform/markers/common/problemMatcher';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
......@@ -516,6 +517,7 @@ interface WorkspaceConfigurationResult {
class TaskService extends EventEmitter implements ITaskService {
// private static autoDetectTelemetryName: string = 'taskServer.autoDetect';
private static RecentlyUsedTasks_Key = 'workbench.tasks.recentlyUsedTasks';
public _serviceBrand: any;
public static SERVICE_ID: string = 'taskService';
......@@ -544,6 +546,7 @@ class TaskService extends EventEmitter implements ITaskService {
private _taskSystem: ITaskSystem;
private _taskSystemListeners: IDisposable[];
private _recentlyUsedTasks: LinkedMap<string, string>;
private _outputChannel: IOutputChannel;
......@@ -559,7 +562,8 @@ class TaskService extends EventEmitter implements ITaskService {
@IEnvironmentService private environmentService: IEnvironmentService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
@ITerminalService private terminalService: ITerminalService,
@IWorkbenchEditorService private workbenchEditorService: IWorkbenchEditorService
@IWorkbenchEditorService private workbenchEditorService: IWorkbenchEditorService,
@IStorageService private storageService: IStorageService
) {
super();
......@@ -689,6 +693,37 @@ class TaskService extends EventEmitter implements ITaskService {
return TPromise.as(this._taskSystem.getActiveTasks());
}
public getRecentlyUsedTasks(): LinkedMap<string, string> {
if (this._recentlyUsedTasks) {
return this._recentlyUsedTasks;
}
this._recentlyUsedTasks = new LinkedMap<string, string>();
let storageValue = this.storageService.get(TaskService.RecentlyUsedTasks_Key, StorageScope.WORKSPACE);
if (storageValue) {
try {
let values: string[] = JSON.parse(storageValue);
if (Array.isArray(values)) {
for (let value of values) {
this._recentlyUsedTasks.set(value, value);
}
}
} catch (error) {
// Ignore. We use the empty result
}
}
return this._recentlyUsedTasks;
}
private saveRecentlyUsedTasks(): void {
if (!this._recentlyUsedTasks) {
return;
}
let values = this._recentlyUsedTasks.values();
if (values.length > 30) {
values = values.slice(0, 30);
}
this.storageService.store(TaskService.RecentlyUsedTasks_Key, JSON.stringify(values), StorageScope.WORKSPACE);
}
public build(): TPromise<ITaskSummary> {
return this.getTaskSets().then((values) => {
......@@ -896,6 +931,7 @@ class TaskService extends EventEmitter implements ITaskService {
throw new TaskError(Severity.Warning, nls.localize('TaskSystem.active', 'There is already a task running. Terminate it first before executing another task.'), TaskErrors.RunningTask);
}
}
this.getRecentlyUsedTasks().set(task.identifier, task.identifier, Touch.First);
return executeResult.promise;
});
});
......@@ -1248,6 +1284,7 @@ class TaskService extends EventEmitter implements ITaskService {
}
public beforeShutdown(): boolean | TPromise<boolean> {
this.saveRecentlyUsedTasks();
if (this._taskSystem && this._taskSystem.isActiveSync()) {
if (this._taskSystem.canAutoTerminate() || this.messageService.confirm({
message: nls.localize('TaskSystem.runningTask', 'There is a task running. Do you want to terminate it?'),
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册