registerDocumentLinkProvider

This commit is contained in:
Johannes Rieken 2016-07-21 16:05:59 +02:00
parent 7c6e9672cd
commit b5287d4621
9 changed files with 221 additions and 22 deletions

54
src/vs/vscode.d.ts vendored
View file

@ -2312,6 +2312,47 @@ declare namespace vscode {
resolveCompletionItem?(item: CompletionItem, token: CancellationToken): CompletionItem | Thenable<CompletionItem>;
}
/**
* A document link is a range in a text document that links to an internal or external resource, like another
* text document or a web site.
*/
export class DocumentLink {
/**
* The range this link applies to.
*/
range: Range;
/**
* The uri this link points to.
*/
target: Uri;
/**
* Creates a new document link.
*
* @param range The range the document link applies to. Must not be empty.
* @param target The uri the document link points to.
*/
constructor(range: Range, target: Uri);
}
/**
* The document link provider defines the contract between extensions and feature of showing
* links in the editor.
*/
export interface DocumentLinkProvider {
/**
* @param document The document in which the command was invoked.
* @param token A cancellation token.
* @return An array of [document links](#DocumentLink) or a thenable that resolves to such. The lack of a result
* can be signaled by returning `undefined`, `null`, or an empty array.
*/
provideDocumentLinks(document: TextDocument, token: CancellationToken): DocumentLink[] | Thenable<DocumentLink[]>;
}
/**
* A tuple of two characters, like a pair of
* opening and closing brackets.
@ -3753,6 +3794,19 @@ declare namespace vscode {
*/
export function registerSignatureHelpProvider(selector: DocumentSelector, provider: SignatureHelpProvider, ...triggerCharacters: string[]): Disposable;
/**
* Register a document link provider.
*
* Multiple providers can be registered for a language. In that case providers are asked in
* parallel and the results are merged. A failing provider (rejected promise or exception) will
* not cause a failure of the whole operation.
*
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider A document link provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerDocumentLinkProvider(selector: DocumentSelector, provider: DocumentLinkProvider): Disposable;
/**
* Set a [language configuration](#LanguageConfiguration) for a language.
*

View file

@ -74,6 +74,7 @@ export class ExtHostAPIImplementation {
CompletionItem: typeof vscode.CompletionItem;
CompletionItemKind: typeof vscode.CompletionItemKind;
CompletionList: typeof vscode.CompletionList;
DocumentLink: typeof vscode.DocumentLink;
IndentAction: typeof vscode.IndentAction;
OverviewRulerLane: typeof vscode.OverviewRulerLane;
TextEditorRevealType: typeof vscode.TextEditorRevealType;
@ -150,6 +151,7 @@ export class ExtHostAPIImplementation {
this.CompletionItem = extHostTypes.CompletionItem;
this.CompletionItemKind = extHostTypes.CompletionItemKind;
this.CompletionList = extHostTypes.CompletionList;
this.DocumentLink = extHostTypes.DocumentLink;
this.ViewColumn = extHostTypes.ViewColumn;
this.StatusBarAlignment = extHostTypes.StatusBarAlignment;
this.IndentAction = Modes.IndentAction;
@ -369,6 +371,9 @@ export class ExtHostAPIImplementation {
registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, ...triggerCharacters: string[]): vscode.Disposable {
return languageFeatures.registerCompletionItemProvider(selector, provider, triggerCharacters);
},
registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable {
return languageFeatures.registerDocumentLinkProvider(selector, provider);
},
setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration):vscode.Disposable => {
return languageFeatures.setLanguageConfiguration(language, configuration);
}

View file

@ -127,6 +127,7 @@ export abstract class MainThreadLanguageFeaturesShape {
$registerRenameSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> { throw ni(); }
$registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[]): TPromise<any> { throw ni(); }
$registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise<any> { throw ni(); }
$registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> { throw ni(); }
$setLanguageConfiguration(handle: number, languageId:string, configuration: vscode.LanguageConfiguration): TPromise<any> { throw ni(); }
}
@ -267,6 +268,8 @@ export abstract class ExtHostLanguageFeaturesShape {
$provideCompletionItems(handle: number, resource: URI, position: editorCommon.IPosition): TPromise<modes.ISuggestResult[]> { throw ni(); }
$resolveCompletionItem(handle: number, resource: URI, position: editorCommon.IPosition, suggestion: modes.ISuggestion): TPromise<modes.ISuggestion> { throw ni(); }
$provideSignatureHelp(handle: number, resource: URI, position: editorCommon.IPosition): TPromise<modes.SignatureHelp> { throw ni(); }
$providDocumentLinks(handle: number, resource: URI): TPromise<modes.ILink[]> { throw ni(); }
}
export abstract class ExtHostQuickOpenShape {

View file

@ -588,10 +588,31 @@ class SignatureHelpAdapter {
}
}
class LinkProviderAdapter {
private _documents: ExtHostDocuments;
private _provider: vscode.DocumentLinkProvider;
constructor(documents: ExtHostDocuments, provider: vscode.DocumentLinkProvider) {
this._documents = documents;
this._provider = provider;
}
provideLinks(resource: URI): TPromise<modes.ILink[]> {
const doc = this._documents.getDocumentData(resource).document;
return asWinJsPromise(token => this._provider.provideDocumentLinks(doc, token)).then(links => {
if (Array.isArray(links)) {
return links.map(TypeConverters.DocumentLink.from);
}
});
}
}
type Adapter = OutlineAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter
| DocumentHighlightAdapter | ReferenceAdapter | QuickFixAdapter | DocumentFormattingAdapter
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
| SuggestAdapter | SignatureHelpAdapter;
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter;
export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape {
@ -821,6 +842,19 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape {
return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(resource, position));
}
// --- links
registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable {
const handle = this._nextHandle();
this._adapter[handle] = new LinkProviderAdapter(this._documents, provider);
this._proxy.$registerDocumentLinkProvider(handle, selector);
return this._createDisposable(handle);
}
$providDocumentLinks(handle: number, resource: URI): TPromise<modes.ILink[]> {
return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.provideLinks(resource));
}
// --- configuration
setLanguageConfiguration(languageId:string, configuration: vscode.LanguageConfiguration): vscode.Disposable {

View file

@ -318,6 +318,19 @@ export namespace SignatureHelp {
}
}
export namespace DocumentLink {
export function from(link: types.DocumentLink): modes.ILink {
return {
range: fromRange(link.range),
url: link.target.toString()
};
}
export function to(link: modes.ILink):types.DocumentLink {
return new types.DocumentLink(toRange(link.range), URI.parse(link.url));
}
}
export namespace Command {

View file

@ -206,6 +206,17 @@ export class Position {
export class Range {
static is(thing: any): thing is Range {
if (thing instanceof Range) {
return true;
}
if (!thing) {
return false;
}
return Position.is((<Range>thing).start)
&& Position.is((<Range>thing.end));
}
protected _start: Position;
protected _end: Position;
@ -769,3 +780,21 @@ export enum TextEditorRevealType {
InCenter = 1,
InCenterIfOutsideViewport = 2
}
export class DocumentLink {
range: Range;
target: URI;
constructor(range: Range, target: URI) {
if (!(target instanceof URI)) {
throw illegalArgument('target');
}
if (!Range.is(range) || range.isEmpty) {
throw illegalArgument('range');
}
this.range = range;
this.target = target;
}
}

View file

@ -201,6 +201,17 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape
return undefined;
}
// --- links
$registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
this._registrations[handle] = modes.LinkProviderRegistry.register(selector, <modes.LinkProvider>{
provideLinks: (model, token) => {
return wireCancellationToken(token, this._proxy.$providDocumentLinks(handle, model.uri));
}
});
return undefined;
}
// --- configuration
$setLanguageConfiguration(handle: number, languageId: string, configuration: vscode.LanguageConfiguration): TPromise<any> {

View file

@ -35,6 +35,7 @@ import {rename} from 'vs/editor/contrib/rename/common/rename';
import {provideSignatureHelp} from 'vs/editor/contrib/parameterHints/common/parameterHints';
import {provideSuggestionItems} from 'vs/editor/contrib/suggest/common/suggest';
import {getDocumentFormattingEdits, getDocumentRangeFormattingEdits, getOnTypeFormattingEdits} from 'vs/editor/contrib/format/common/format';
import {getLinks} from 'vs/editor/contrib/links/common/links';
import {asWinJsPromise} from 'vs/base/common/async';
import {MainContext, ExtHostContext} from 'vs/workbench/api/node/extHost.protocol';
import {ExtHostDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics';
@ -949,4 +950,48 @@ suite('ExtHostLanguageFeatures', function() {
});
});
});
test('Links, data conversion', function () {
disposables.push(extHost.registerDocumentLinkProvider(defaultSelector, <vscode.DocumentLinkProvider>{
provideDocumentLinks() {
return [new types.DocumentLink(new types.Range(0, 0, 1, 1), types.Uri.parse('foo:bar#3'))];
}
}));
return threadService.sync().then(() => {
return getLinks(model).then(value => {
assert.equal(value.length, 1);
let [first] = value;
assert.equal(first.url, 'foo:bar#3');
assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 });
});
});
});
test('Links, evil provider', function () {
disposables.push(extHost.registerDocumentLinkProvider(defaultSelector, <vscode.DocumentLinkProvider>{
provideDocumentLinks() {
return [new types.DocumentLink(new types.Range(0, 0, 1, 1), types.Uri.parse('foo:bar#3'))];
}
}));
disposables.push(extHost.registerDocumentLinkProvider(defaultSelector, <vscode.DocumentLinkProvider>{
provideDocumentLinks(): any {
throw new Error();
}
}));
return threadService.sync().then(() => {
return getLinks(model).then(value => {
assert.equal(value.length, 1);
let [first] = value;
assert.equal(first.url, 'foo:bar#3');
assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 });
});
});
});
});

View file

@ -16,9 +16,9 @@ function assertToJSON(a: any, expected: any) {
assert.deepEqual(actual, expected);
}
suite('ExtHostTypes', function() {
suite('ExtHostTypes', function () {
test('URI, toJSON', function() {
test('URI, toJSON', function () {
let uri = URI.parse('file:///path/test.file');
let data = uri.toJSON();
@ -34,7 +34,7 @@ suite('ExtHostTypes', function() {
});
});
test('Disposable', function() {
test('Disposable', function () {
let count = 0;
let d = new types.Disposable(() => {
@ -61,7 +61,7 @@ suite('ExtHostTypes', function() {
});
test('Position', function() {
test('Position', function () {
assert.throws(() => new types.Position(-1, 0));
assert.throws(() => new types.Position(0, -1));
@ -75,12 +75,12 @@ suite('ExtHostTypes', function() {
assert.equal(character, 0);
});
test('Position, toJSON', function() {
test('Position, toJSON', function () {
let pos = new types.Position(4, 2);
assertToJSON(pos, { line: 4, character: 2 });
});
test('Position, isBefore(OrEqual)?', function() {
test('Position, isBefore(OrEqual)?', function () {
let p1 = new types.Position(1, 3);
let p2 = new types.Position(1, 2);
let p3 = new types.Position(0, 4);
@ -91,7 +91,7 @@ suite('ExtHostTypes', function() {
assert.ok(p3.isBefore(p2));
});
test('Position, isAfter(OrEqual)?', function() {
test('Position, isAfter(OrEqual)?', function () {
let p1 = new types.Position(1, 3);
let p2 = new types.Position(1, 2);
let p3 = new types.Position(0, 4);
@ -103,7 +103,7 @@ suite('ExtHostTypes', function() {
assert.ok(p1.isAfter(p3));
});
test('Position, compareTo', function() {
test('Position, compareTo', function () {
let p1 = new types.Position(1, 3);
let p2 = new types.Position(1, 2);
let p3 = new types.Position(0, 4);
@ -115,7 +115,7 @@ suite('ExtHostTypes', function() {
assert.equal(p1.compareTo(p3), 1);
});
test('Position, translate', function() {
test('Position, translate', function () {
let p1 = new types.Position(1, 3);
assert.ok(p1.translate() === p1);
@ -153,7 +153,7 @@ suite('ExtHostTypes', function() {
assert.throws(() => p1.translate(0, -4));
});
test('Position, with', function() {
test('Position, with', function () {
let p1 = new types.Position(1, 3);
assert.ok(p1.with() === p1);
@ -176,7 +176,7 @@ suite('ExtHostTypes', function() {
assert.throws(() => p1.with({ character: -1 }));
});
test('Range', function() {
test('Range', function () {
assert.throws(() => new types.Range(-1, 0, 0, 0));
assert.throws(() => new types.Range(0, -1, 0, 0));
assert.throws(() => new types.Range(new types.Position(0, 0), undefined));
@ -189,13 +189,13 @@ suite('ExtHostTypes', function() {
assert.throws(() => range.start = new types.Position(0, 3));
});
test('Range, toJSON', function() {
test('Range, toJSON', function () {
let range = new types.Range(1, 2, 3, 4);
assertToJSON(range, [{ line: 1, character: 2 }, { line: 3, character: 4 }]);
});
test('Range, sorting', function() {
test('Range, sorting', function () {
// sorts start/end
let range = new types.Range(1, 0, 0, 0);
assert.equal(range.start.line, 0);
@ -206,7 +206,7 @@ suite('ExtHostTypes', function() {
assert.equal(range.end.line, 1);
});
test('Range, isEmpty|isSingleLine', function() {
test('Range, isEmpty|isSingleLine', function () {
let range = new types.Range(1, 0, 0, 0);
assert.ok(!range.isEmpty);
assert.ok(!range.isSingleLine);
@ -224,7 +224,7 @@ suite('ExtHostTypes', function() {
assert.ok(!range.isSingleLine);
});
test('Range, contains', function() {
test('Range, contains', function () {
let range = new types.Range(1, 1, 2, 11);
assert.ok(range.contains(range.start));
@ -237,7 +237,7 @@ suite('ExtHostTypes', function() {
assert.ok(!range.contains(new types.Range(1, 1, 3, 11)));
});
test('Range, intersection', function() {
test('Range, intersection', function () {
let range = new types.Range(1, 1, 2, 11);
let res: types.Range;
@ -267,7 +267,7 @@ suite('ExtHostTypes', function() {
assert.throws(() => range.intersection(undefined));
});
test('Range, union', function() {
test('Range, union', function () {
let ran1 = new types.Range(0, 0, 5, 5);
assert.ok(ran1.union(new types.Range(0, 0, 1, 1)) === ran1);
@ -284,7 +284,7 @@ suite('ExtHostTypes', function() {
assert.equal(res.start.character, 0);
});
test('Range, with', function() {
test('Range, with', function () {
let range = new types.Range(1, 1, 2, 11);
assert.ok(range.with(range.start) === range);
@ -310,7 +310,7 @@ suite('ExtHostTypes', function() {
assert.equal(res.start.line, 1);
assert.equal(res.start.character, 1);
res = range.with({ end: new types.Position(9, 8), start: new types.Position(2, 3)});
res = range.with({ end: new types.Position(9, 8), start: new types.Position(2, 3) });
assert.equal(res.end.line, 9);
assert.equal(res.end.character, 8);
assert.equal(res.start.line, 2);
@ -320,7 +320,7 @@ suite('ExtHostTypes', function() {
assert.throws(() => range.with(undefined, null));
});
test('TextEdit', function() {
test('TextEdit', function () {
assert.throws(() => new types.TextEdit(null, 'far'));
assert.throws(() => new types.TextEdit(undefined, 'far'));
@ -337,7 +337,7 @@ suite('ExtHostTypes', function() {
assert.equal(edit.newText, '');
});
test('WorkspaceEdit', function() {
test('WorkspaceEdit', function () {
let a = types.Uri.file('a.ts');
let b = types.Uri.file('b.ts');
@ -368,6 +368,11 @@ suite('ExtHostTypes', function() {
});
test('DocumentLink', function () {
assert.throws(() => new types.DocumentLink(null, null));
assert.throws(() => new types.DocumentLink(new types.Range(1, 1, 1, 1), null));
});
test('toJSON & stringify', function() {
assertToJSON(new types.Selection(3, 4, 2, 1), { start: { line: 2, character: 1 }, end: { line: 3, character: 4 }, anchor: { line: 3, character: 4 }, active: { line: 2, character: 1 } });