提交 272d558c 编写于 作者: J Johannes Rieken

debt - add ILinksList to LinksProvider and allow it to define a lifecycle

上级 5e0c911e
......@@ -1001,11 +1001,16 @@ export interface ILink {
range: IRange;
url?: URI | string;
}
export interface ILinksList {
links: ILink[];
dispose?(): void;
}
/**
* A provider of links.
*/
export interface LinkProvider {
provideLinks(model: model.ITextModel, token: CancellationToken): ProviderResult<ILink[]>;
provideLinks(model: model.ITextModel, token: CancellationToken): ProviderResult<ILinksList>;
resolveLink?: (link: ILink, token: CancellationToken) => ProviderResult<ILink>;
}
......
......@@ -58,12 +58,14 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker
this._workerManager = this._register(new WorkerManager(this._modelService));
// todo@joh make sure this happens only once
this._register(modes.LinkProviderRegistry.register('*', <modes.LinkProvider>{
this._register(modes.LinkProviderRegistry.register('*', {
provideLinks: (model, token) => {
if (!canSyncModel(this._modelService, model.uri)) {
return Promise.resolve([]); // File too large
return Promise.resolve({ links: [] }); // File too large
}
return this._workerManager.withWorker().then(client => client.computeLinks(model.uri));
return this._workerManager.withWorker().then(client => client.computeLinks(model.uri)).then(links => {
return links && { links };
});
}
}));
this._register(modes.CompletionProviderRegistry.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService)));
......
......@@ -8,9 +8,11 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { ILink, LinkProvider, LinkProviderRegistry } from 'vs/editor/common/modes';
import { ILink, LinkProvider, LinkProviderRegistry, ILinksList } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { isDisposable, Disposable } from 'vs/base/common/lifecycle';
import { coalesce } from 'vs/base/common/arrays';
export class Link implements ILink {
......@@ -66,77 +68,99 @@ export class Link implements ILink {
}
}
export function getLinks(model: ITextModel, token: CancellationToken): Promise<Link[]> {
export class LinksList extends Disposable {
let links: Link[] = [];
readonly links: Link[];
// ask all providers for links in parallel
const promises = LinkProviderRegistry.ordered(model).reverse().map(provider => {
return Promise.resolve(provider.provideLinks(model, token)).then(result => {
if (Array.isArray(result)) {
const newLinks = result.map(link => new Link(link, provider));
links = union(links, newLinks);
constructor(tuples: [ILinksList, LinkProvider][]) {
super();
let links: Link[] = [];
for (const [list, provider] of tuples) {
// merge all links
const newLinks = list.links.map(link => new Link(link, provider));
links = LinksList._union(links, newLinks);
// register disposables
if (isDisposable(provider)) {
this._register(provider);
}
}, onUnexpectedExternalError);
});
}
this.links = links;
}
return Promise.all(promises).then(() => {
return links;
});
}
private static _union(oldLinks: Link[], newLinks: Link[]): Link[] {
// reunite oldLinks with newLinks and remove duplicates
let result: Link[] = [];
let oldIndex: number;
let oldLen: number;
let newIndex: number;
let newLen: number;
for (oldIndex = 0, newIndex = 0, oldLen = oldLinks.length, newLen = newLinks.length; oldIndex < oldLen && newIndex < newLen;) {
const oldLink = oldLinks[oldIndex];
const newLink = newLinks[newIndex];
if (Range.areIntersectingOrTouching(oldLink.range, newLink.range)) {
// Remove the oldLink
oldIndex++;
continue;
}
function union(oldLinks: Link[], newLinks: Link[]): Link[] {
// reunite oldLinks with newLinks and remove duplicates
let result: Link[] = [];
let oldIndex: number;
let oldLen: number;
let newIndex: number;
let newLen: number;
for (oldIndex = 0, newIndex = 0, oldLen = oldLinks.length, newLen = newLinks.length; oldIndex < oldLen && newIndex < newLen;) {
const oldLink = oldLinks[oldIndex];
const newLink = newLinks[newIndex];
if (Range.areIntersectingOrTouching(oldLink.range, newLink.range)) {
// Remove the oldLink
oldIndex++;
continue;
}
const comparisonResult = Range.compareRangesUsingStarts(oldLink.range, newLink.range);
const comparisonResult = Range.compareRangesUsingStarts(oldLink.range, newLink.range);
if (comparisonResult < 0) {
// oldLink is before
result.push(oldLink);
oldIndex++;
} else {
// newLink is before
result.push(newLink);
newIndex++;
}
}
if (comparisonResult < 0) {
// oldLink is before
result.push(oldLink);
oldIndex++;
} else {
// newLink is before
result.push(newLink);
newIndex++;
for (; oldIndex < oldLen; oldIndex++) {
result.push(oldLinks[oldIndex]);
}
for (; newIndex < newLen; newIndex++) {
result.push(newLinks[newIndex]);
}
}
for (; oldIndex < oldLen; oldIndex++) {
result.push(oldLinks[oldIndex]);
}
for (; newIndex < newLen; newIndex++) {
result.push(newLinks[newIndex]);
return result;
}
return result;
}
CommandsRegistry.registerCommand('_executeLinkProvider', (accessor, ...args) => {
export function getLinks(model: ITextModel, token: CancellationToken): Promise<LinksList> {
const lists: [ILinksList, LinkProvider][] = [];
// ask all providers for links in parallel
const promises = LinkProviderRegistry.ordered(model).reverse().map((provider, i) => {
return Promise.resolve(provider.provideLinks(model, token)).then(result => {
if (result) {
lists[i] = [result, provider];
}
}, onUnexpectedExternalError);
});
return Promise.all(promises).then(() => new LinksList(coalesce(lists)));
}
CommandsRegistry.registerCommand('_executeLinkProvider', async (accessor, ...args): Promise<ILink[]> => {
const [uri] = args;
if (!(uri instanceof URI)) {
return undefined;
return [];
}
const model = accessor.get(IModelService).getModel(uri);
if (!model) {
return undefined;
return [];
}
return getLinks(model, CancellationToken.None);
const list = await getLinks(model, CancellationToken.None);
if (!list) {
return [];
}
const result = list.links.slice(0);
list.dispose();
return result;
});
......@@ -19,7 +19,7 @@ import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, TrackedRangeSti
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { LinkProviderRegistry } from 'vs/editor/common/modes';
import { ClickLinkGesture, ClickLinkKeyboardEvent, ClickLinkMouseEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture';
import { Link, getLinks } from 'vs/editor/contrib/links/getLinks';
import { Link, getLinks, LinksList } from 'vs/editor/contrib/links/getLinks';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry';
......@@ -157,7 +157,8 @@ class LinkDetector implements editorCommon.IEditorContribution {
private enabled: boolean;
private listenersToRemove: IDisposable[];
private readonly timeout: async.TimeoutTimer;
private computePromise: async.CancelablePromise<Link[]> | null;
private computePromise: async.CancelablePromise<LinksList> | null;
private activeLinksList: LinksList | null;
private activeLinkDecorationId: string | null;
private readonly openerService: IOpenerService;
private readonly notificationService: INotificationService;
......@@ -210,6 +211,7 @@ class LinkDetector implements editorCommon.IEditorContribution {
this.timeout = new async.TimeoutTimer();
this.computePromise = null;
this.activeLinksList = null;
this.currentOccurrences = {};
this.activeLinkDecorationId = null;
this.beginCompute();
......@@ -246,10 +248,15 @@ class LinkDetector implements editorCommon.IEditorContribution {
return;
}
if (this.activeLinksList) {
this.activeLinksList.dispose();
this.activeLinksList = null;
}
this.computePromise = async.createCancelablePromise(token => getLinks(model, token));
try {
const links = await this.computePromise;
this.updateDecorations(links);
this.activeLinksList = await this.computePromise;
this.updateDecorations(this.activeLinksList.links);
} catch (err) {
onUnexpectedError(err);
} finally {
......@@ -380,6 +387,9 @@ class LinkDetector implements editorCommon.IEditorContribution {
private stop(): void {
this.timeout.cancel();
if (this.activeLinksList) {
this.activeLinksList.dispose();
}
if (this.computePromise) {
this.computePromise.cancel();
this.computePromise = null;
......
......@@ -5253,11 +5253,16 @@ declare namespace monaco.languages {
url?: Uri | string;
}
export interface ILinksList {
links: ILink[];
dispose?(): void;
}
/**
* A provider of links.
*/
export interface LinkProvider {
provideLinks(model: editor.ITextModel, token: CancellationToken): ProviderResult<ILink[]>;
provideLinks(model: editor.ITextModel, token: CancellationToken): ProviderResult<ILinksList>;
resolveLink?: (link: ILink, token: CancellationToken) => ProviderResult<ILink>;
}
......
......@@ -428,7 +428,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
// --- links
$registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void {
this._registrations[handle] = modes.LinkProviderRegistry.register(selector, <modes.LinkProvider>{
this._registrations[handle] = modes.LinkProviderRegistry.register(selector, {
provideLinks: (model, token) => {
return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => {
if (dto) {
......@@ -437,7 +437,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
this._heapService.trackObject(obj);
});
}
return dto;
return { links: dto as modes.ILink[] };
});
},
resolveLink: (link, token) => {
......@@ -446,7 +446,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
MainThreadLanguageFeatures._reviveLinkDTO(obj);
this._heapService.trackObject(obj);
}
return obj;
return obj as modes.ILink;
});
}
});
......
......@@ -6,7 +6,7 @@
import { URI } from 'vs/base/common/uri';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IModelService } from 'vs/editor/common/services/modelService';
import { LinkProviderRegistry, ILink } from 'vs/editor/common/modes';
import { LinkProviderRegistry, ILink, ILinksList } from 'vs/editor/common/modes';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/contrib/output/common/output';
import { MonacoWebWorker, createWebWorker } from 'vs/editor/common/services/webWorker';
......@@ -42,8 +42,8 @@ export class OutputLinkProvider {
if (folders.length > 0) {
if (!this.linkProviderRegistration) {
this.linkProviderRegistration = LinkProviderRegistry.register([{ language: OUTPUT_MODE_ID, scheme: '*' }, { language: LOG_MODE_ID, scheme: '*' }], {
provideLinks: (model, token): Promise<ILink[]> => {
return this.provideLinks(model.uri);
provideLinks: (model): Promise<ILinksList> => {
return this.provideLinks(model.uri).then(links => links && { links });
}
});
}
......
......@@ -1046,9 +1046,9 @@ suite('ExtHostLanguageFeatures', function () {
}));
await rpcProtocol.sync();
let value = await getLinks(model, CancellationToken.None);
assert.equal(value.length, 1);
let [first] = value;
let { links } = await getLinks(model, CancellationToken.None);
assert.equal(links.length, 1);
let [first] = links;
assert.equal(first.url, 'foo:bar#3');
assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 });
});
......@@ -1068,9 +1068,9 @@ suite('ExtHostLanguageFeatures', function () {
}));
await rpcProtocol.sync();
let value = await getLinks(model, CancellationToken.None);
assert.equal(value.length, 1);
let [first] = value;
let { links } = await getLinks(model, CancellationToken.None);
assert.equal(links.length, 1);
let [first] = links;
assert.equal(first.url, 'foo:bar#3');
assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 });
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册