add 'vscode.executeLinkProvider' command
This commit is contained in:
parent
c1b999fdb0
commit
d9067043bc
6 changed files with 130 additions and 100 deletions
|
@ -8,7 +8,7 @@ import URI from 'vs/base/common/uri';
|
|||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
import {createDecorator} from 'vs/platform/instantiation/common/instantiation';
|
||||
import {IChange, ILineChange, IPosition, IRange} from 'vs/editor/common/editorCommon';
|
||||
import {IInplaceReplaceSupportResult, ILink, ISuggestResult} from 'vs/editor/common/modes';
|
||||
import {IInplaceReplaceSupportResult, ISuggestResult} from 'vs/editor/common/modes';
|
||||
|
||||
export var ID_EDITOR_WORKER_SERVICE = 'editorWorkerService';
|
||||
export var IEditorWorkerService = createDecorator<IEditorWorkerService>(ID_EDITOR_WORKER_SERVICE);
|
||||
|
@ -18,7 +18,6 @@ export interface IEditorWorkerService {
|
|||
|
||||
computeDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise<ILineChange[]>;
|
||||
computeDirtyDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise<IChange[]>;
|
||||
computeLinks(resource:URI):TPromise<ILink[]>;
|
||||
textualSuggest(resource: URI, position: IPosition): TPromise<ISuggestResult[]>;
|
||||
navigateValueSet(resource: URI, range:IRange, up:boolean): TPromise<IInplaceReplaceSupportResult>;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {IntervalTimer, ShallowCancelThenPromise} from 'vs/base/common/async';
|
||||
import {IntervalTimer, ShallowCancelThenPromise, wireCancellationToken} from 'vs/base/common/async';
|
||||
import {Disposable, IDisposable, dispose} from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
|
@ -12,7 +12,7 @@ import {SimpleWorkerClient} from 'vs/base/common/worker/simpleWorker';
|
|||
import {DefaultWorkerFactory} from 'vs/base/worker/defaultWorkerFactory';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import {WordHelper} from 'vs/editor/common/model/textModelWithTokensHelpers';
|
||||
import {IInplaceReplaceSupportResult, ILink, ISuggestResult} from 'vs/editor/common/modes';
|
||||
import {IInplaceReplaceSupportResult, ILink, ISuggestResult, LinkProviderRegistry, LinkProvider} from 'vs/editor/common/modes';
|
||||
import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService';
|
||||
import {IModelService} from 'vs/editor/common/services/modelService';
|
||||
import {EditorSimpleWorkerImpl} from 'vs/editor/common/services/editorSimpleWorker';
|
||||
|
@ -31,10 +31,23 @@ const STOP_WORKER_DELTA_TIME_MS = 5 * 60 * 1000;
|
|||
export class EditorWorkerServiceImpl implements IEditorWorkerService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private _workerManager:WorkerManager;
|
||||
private _workerManager: WorkerManager;
|
||||
private _registration: IDisposable;
|
||||
|
||||
constructor(@IModelService modelService:IModelService) {
|
||||
this._workerManager = new WorkerManager(modelService);
|
||||
|
||||
// todo@joh make sure this happens only once
|
||||
this._registration = LinkProviderRegistry.register('*', <LinkProvider>{
|
||||
provideLinks: (model, token) => {
|
||||
return wireCancellationToken(token, this._workerManager.withWorker().then(client => client.computeLinks(model.uri)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._workerManager.dispose();
|
||||
this._registration.dispose();
|
||||
}
|
||||
|
||||
public computeDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise<editorCommon.ILineChange[]> {
|
||||
|
@ -45,10 +58,6 @@ export class EditorWorkerServiceImpl implements IEditorWorkerService {
|
|||
return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace));
|
||||
}
|
||||
|
||||
public computeLinks(resource:URI):TPromise<ILink[]> {
|
||||
return this._workerManager.withWorker().then(client => client.computeLinks(resource));
|
||||
}
|
||||
|
||||
public textualSuggest(resource: URI, position: editorCommon.IPosition): TPromise<ISuggestResult[]> {
|
||||
return this._workerManager.withWorker().then(client => client.textualSuggest(resource, position));
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import {TPromise} from 'vs/base/common/winjs.base';
|
|||
import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent';
|
||||
import {IEditorService, IResourceInput} from 'vs/platform/editor/common/editor';
|
||||
import {IMessageService} from 'vs/platform/message/common/message';
|
||||
import {Range} from 'vs/editor/common/core/range';
|
||||
import {EditorAction} from 'vs/editor/common/editorAction';
|
||||
import {Behaviour} from 'vs/editor/common/editorActionEnablement';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
|
@ -75,16 +74,6 @@ class LinkOccurence {
|
|||
}
|
||||
}
|
||||
|
||||
class Link {
|
||||
range: Range;
|
||||
url: string;
|
||||
|
||||
constructor(source:ILink) {
|
||||
this.range = new Range(source.range.startLineNumber, source.range.startColumn, source.range.endLineNumber, source.range.endColumn);
|
||||
this.url = source.url;
|
||||
}
|
||||
}
|
||||
|
||||
class LinkDetector implements editorCommon.IEditorContribution {
|
||||
|
||||
public static ID: string = 'editor.linkDetector';
|
||||
|
@ -102,7 +91,7 @@ class LinkDetector implements editorCommon.IEditorContribution {
|
|||
private editor:ICodeEditor;
|
||||
private listenersToRemove:IDisposable[];
|
||||
private timeoutPromise:TPromise<void>;
|
||||
private computePromise:TPromise<ILink[]>;
|
||||
private computePromise:TPromise<void>;
|
||||
private activeLinkDecorationId:string;
|
||||
private lastMouseEvent:IEditorMouseEvent;
|
||||
private editorService:IEditorService;
|
||||
|
@ -172,75 +161,16 @@ class LinkDetector implements editorCommon.IEditorContribution {
|
|||
return;
|
||||
}
|
||||
|
||||
let modePromise:TPromise<ILink[]> = TPromise.as(null);
|
||||
if (LinkProviderRegistry.has(this.editor.getModel())) {
|
||||
modePromise = getLinks(this.editor.getModel());
|
||||
if (!LinkProviderRegistry.has(this.editor.getModel())) {
|
||||
return;
|
||||
}
|
||||
|
||||
let standardPromise:TPromise<ILink[]> = this.editorWorkerService.computeLinks(this.editor.getModel().uri);
|
||||
|
||||
this.computePromise = TPromise.join([modePromise, standardPromise]).then((r) => {
|
||||
let a = r[0];
|
||||
let b = r[1];
|
||||
if (!a || a.length === 0) {
|
||||
return b || [];
|
||||
}
|
||||
if (!b || b.length === 0) {
|
||||
return a || [];
|
||||
}
|
||||
return LinkDetector._linksUnion(a.map(el => new Link(el)), b.map(el => new Link(el)));
|
||||
});
|
||||
|
||||
this.computePromise.then((links:ILink[]) => {
|
||||
this.computePromise = getLinks(this.editor.getModel()).then(links => {
|
||||
this.updateDecorations(links);
|
||||
this.computePromise = null;
|
||||
});
|
||||
}
|
||||
|
||||
private static _linksUnion(oldLinks: Link[], newLinks: Link[]): Link[] {
|
||||
// reunite oldLinks with newLinks and remove duplicates
|
||||
var result: Link[] = [],
|
||||
oldIndex: number,
|
||||
oldLen: number,
|
||||
newIndex: number,
|
||||
newLen: number,
|
||||
oldLink: Link,
|
||||
newLink: Link,
|
||||
comparisonResult: number;
|
||||
|
||||
for (oldIndex = 0, newIndex = 0, oldLen = oldLinks.length, newLen = newLinks.length; oldIndex < oldLen && newIndex < newLen;) {
|
||||
oldLink = oldLinks[oldIndex];
|
||||
newLink = newLinks[newIndex];
|
||||
|
||||
if (Range.areIntersectingOrTouching(oldLink.range, newLink.range)) {
|
||||
// Remove the oldLink
|
||||
oldIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
for (; oldIndex < oldLen; oldIndex++) {
|
||||
result.push(oldLinks[oldIndex]);
|
||||
}
|
||||
for (; newIndex < newLen; newIndex++) {
|
||||
result.push(newLinks[newIndex]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private updateDecorations(links:ILink[]):void {
|
||||
this.editor.changeDecorations((changeAccessor:editorCommon.IModelDecorationsChangeAccessor) => {
|
||||
var oldDecorations:string[] = [];
|
||||
|
|
|
@ -6,32 +6,88 @@
|
|||
'use strict';
|
||||
|
||||
import {onUnexpectedError} from 'vs/base/common/errors';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
import {Range} from 'vs/editor/common/core/range';
|
||||
import {IReadOnlyModel} from 'vs/editor/common/editorCommon';
|
||||
import {ILink, LinkProviderRegistry} from 'vs/editor/common/modes';
|
||||
import {asWinJsPromise} from 'vs/base/common/async';
|
||||
import {CommandsRegistry} from 'vs/platform/commands/common/commands';
|
||||
import {IModelService} from 'vs/editor/common/services/modelService';
|
||||
|
||||
export function getLinks(model: IReadOnlyModel): TPromise<ILink[]> {
|
||||
|
||||
const promises = LinkProviderRegistry.ordered(model).map((support) => {
|
||||
return asWinJsPromise((token) => {
|
||||
return support.provideLinks(model, token);
|
||||
}).then((result) => {
|
||||
let links: ILink[] = [];
|
||||
|
||||
// ask all providers for links in parallel
|
||||
const promises = LinkProviderRegistry.ordered(model).reverse().map(support => {
|
||||
return asWinJsPromise(token => support.provideLinks(model, token)).then(result => {
|
||||
if (Array.isArray(result)) {
|
||||
return <ILink[]> result;
|
||||
links = union(links, result);
|
||||
}
|
||||
}, err => {
|
||||
onUnexpectedError(err);
|
||||
});
|
||||
}, onUnexpectedError);
|
||||
});
|
||||
|
||||
return TPromise.join(promises).then(manyLinks => {
|
||||
let result: ILink[] = [];
|
||||
for (let links of manyLinks) {
|
||||
if (links) {
|
||||
result = result.concat(links);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return TPromise.join(promises).then(() => {
|
||||
return links;
|
||||
});
|
||||
}
|
||||
|
||||
function union(oldLinks: ILink[], newLinks: ILink[]): ILink[] {
|
||||
// reunite oldLinks with newLinks and remove duplicates
|
||||
var result: ILink[] = [],
|
||||
oldIndex: number,
|
||||
oldLen: number,
|
||||
newIndex: number,
|
||||
newLen: number,
|
||||
oldLink: ILink,
|
||||
newLink: ILink,
|
||||
comparisonResult: number;
|
||||
|
||||
for (oldIndex = 0, newIndex = 0, oldLen = oldLinks.length, newLen = newLinks.length; oldIndex < oldLen && newIndex < newLen;) {
|
||||
oldLink = oldLinks[oldIndex];
|
||||
newLink = newLinks[newIndex];
|
||||
|
||||
if (Range.areIntersectingOrTouching(oldLink.range, newLink.range)) {
|
||||
// Remove the oldLink
|
||||
oldIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
for (; oldIndex < oldLen; oldIndex++) {
|
||||
result.push(oldLinks[oldIndex]);
|
||||
}
|
||||
for (; newIndex < newLen; newIndex++) {
|
||||
result.push(newLinks[newIndex]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_executeLinkProvider', (accessor, ...args) => {
|
||||
|
||||
const [uri] = args;
|
||||
if (!(uri instanceof URI)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = accessor.get(IModelService).getModel(uri);
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
return getLinks(model);
|
||||
});
|
|
@ -147,7 +147,13 @@ class ExtHostApiCommands {
|
|||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
|
||||
this._register('vscode.executeLinkProvider', this._executeDocumentLinkProvider, {
|
||||
description: 'Execute document link provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of DocumentLink-instances.'
|
||||
});
|
||||
|
||||
this._register('vscode.previewHtml', (uri: URI, position?: vscode.ViewColumn, label?: string) => {
|
||||
return this._commands.executeCommand('_workbench.previewHtml',
|
||||
|
@ -417,4 +423,12 @@ class ExtHostApiCommands {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentLinkProvider(resource: URI): Thenable<vscode.DocumentLink[]> {
|
||||
return this._commands.executeCommand<modes.ILink[]>('_executeLinkProvider', resource).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(typeConverters.DocumentLink.to);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -401,4 +401,26 @@ suite('ExtHostLanguageFeatureCommands', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Links, back and forth', function() {
|
||||
|
||||
disposables.push(extHost.registerDocumentLinkProvider(defaultSelector, <vscode.DocumentLinkProvider>{
|
||||
provideDocumentLinks(): any {
|
||||
return [new types.DocumentLink(new types.Range(0, 0, 0, 20), URI.parse('foo:bar'))];
|
||||
}
|
||||
}));
|
||||
|
||||
return threadService.sync().then(() => {
|
||||
return commands.executeCommand<vscode.DocumentLink[]>('vscode.executeLinkProvider', model.uri).then(value => {
|
||||
assert.equal(value.length, 1);
|
||||
let [first] = value;
|
||||
|
||||
assert.equal(first.target.toString(), 'foo:bar');
|
||||
assert.equal(first.range.start.line, 0);
|
||||
assert.equal(first.range.start.character, 0);
|
||||
assert.equal(first.range.end.line, 0);
|
||||
assert.equal(first.range.end.character, 20);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue