diff --git a/contentprovider-sample/README.md b/contentprovider-sample/README.md index 0794660d..85de7853 100644 --- a/contentprovider-sample/README.md +++ b/contentprovider-sample/README.md @@ -11,13 +11,13 @@ It is not intended as a product quality extension. ![Print References](https://raw.githubusercontent.com/Microsoft/vscode-extension-samples/master/contentprovider-sample/preview.gif) -# How it works +# How it works, what it shows? - The extension implements and registers a [`TextDocumentContentProvider`](http://code.visualstudio.com/docs/extensionAPI/vscode-api#TextDocumentContentProvider) for a particular URI scheme. - The content provider uses the [`vscode.executeReferenceProvider`](http://code.visualstudio.com/docs/extensionAPI/vscode-api-commands)-API command to delegate searching for references to the language extensions, like TypeScript, vscode-go, or C# - The generated document initially contains a caption only and incrementally updates as each reference location is resolved. -- The content provider uses the decoration API to highlight matches inside the generated document - +- Add links for each result in the virtual document pointing to the reference. +- Add an entry to editor context menu via `package.json` # How to run locally diff --git a/contentprovider-sample/package.json b/contentprovider-sample/package.json index bbe5a03f..96afafa7 100644 --- a/contentprovider-sample/package.json +++ b/contentprovider-sample/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/Microsoft/vscode-extension-samples/issues" }, "engines": { - "vscode": "^1.0.0" + "vscode": "^1.4.0" }, "categories": [ "Other" @@ -28,6 +28,15 @@ "title": "Show All References" } ], + "menus": { + "editor/context": [ + { + "command": "editor.printReferences", + "when": "editorHasReferenceProvider", + "group": "navigation@1.31" + } + ] + }, "languages": [ { "id": "locations", diff --git a/contentprovider-sample/preview.gif b/contentprovider-sample/preview.gif index 2b82c10d..f3b2de92 100644 Binary files a/contentprovider-sample/preview.gif and b/contentprovider-sample/preview.gif differ diff --git a/contentprovider-sample/src/extension.ts b/contentprovider-sample/src/extension.ts index 077f7640..84f657df 100644 --- a/contentprovider-sample/src/extension.ts +++ b/contentprovider-sample/src/extension.ts @@ -3,15 +3,19 @@ *--------------------------------------------------------*/ 'use strict'; -import {workspace, window, commands, ExtensionContext} from 'vscode'; -import ContentProvider, {encodeLocation} from './contentProvider'; +import {workspace, languages, window, commands, ExtensionContext, Disposable} from 'vscode'; +import ContentProvider, {encodeLocation} from './provider'; export function activate(context: ExtensionContext) { - const contentProvider = new ContentProvider(); + const provider = new ContentProvider(); // register content provider for scheme `references` - const providerRegistration = workspace.registerTextDocumentContentProvider(ContentProvider.scheme, contentProvider); + // register document link provider for scheme `references` + const providerRegistrations = Disposable.from( + workspace.registerTextDocumentContentProvider(ContentProvider.scheme, provider), + languages.registerDocumentLinkProvider({ scheme: ContentProvider.scheme }, provider) + ); // register command that crafts an uri with the `references` scheme, // open the dynamic document, and shows it in the next editor @@ -21,8 +25,8 @@ export function activate(context: ExtensionContext) { }); context.subscriptions.push( - contentProvider, + provider, commandRegistration, - providerRegistration + providerRegistrations ); } diff --git a/contentprovider-sample/src/contentProvider.ts b/contentprovider-sample/src/provider.ts similarity index 74% rename from contentprovider-sample/src/contentProvider.ts rename to contentprovider-sample/src/provider.ts index efe09ef2..b8e5c057 100644 --- a/contentprovider-sample/src/contentProvider.ts +++ b/contentprovider-sample/src/provider.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import ReferencesDocument from './referencesDocument'; -export default class ContentProvider implements vscode.TextDocumentContentProvider { +export default class Provider implements vscode.TextDocumentContentProvider, vscode.DocumentLinkProvider { static scheme = 'references'; @@ -19,11 +19,7 @@ export default class ContentProvider implements vscode.TextDocumentContentProvid // Listen to the following events: // * closeTextDocument - which means we must clear the corresponding model object - `ReferencesDocument` - // * changeActiveEditor - do decorate with references information - this._subscriptions = vscode.Disposable.from( - vscode.workspace.onDidCloseTextDocument(doc => this._documents.delete(doc.uri.toString())), - vscode.window.onDidChangeActiveTextEditor(this._decorateEditor, this) - ); + this._subscriptions = vscode.workspace.onDidCloseTextDocument(doc => this._documents.delete(doc.uri.toString())); } dispose() { @@ -63,7 +59,7 @@ export default class ContentProvider implements vscode.TextDocumentContentProvid // sort by locations and shuffle to begin from target resource let idx = 0; - locations.sort(ContentProvider._compareLocations).find((loc, i) => loc.uri.toString() === target.toString() && (idx = i) && true); + locations.sort(Provider._compareLocations).find((loc, i) => loc.uri.toString() === target.toString() && (idx = i) && true); locations.push(...locations.splice(0, idx)); // create document and return its early state @@ -73,18 +69,6 @@ export default class ContentProvider implements vscode.TextDocumentContentProvid }); } - private _decorateEditor(editor: vscode.TextEditor) { - // When an editor opens, check if it shows a `location` document - // and decorate the actual references - if (!editor || !vscode.languages.match('locations', editor.document)) { - return; - } - let doc = this._documents.get(editor.document.uri.toString()); - if (doc) { - doc.join().then(() => editor.setDecorations(this._editorDecoration, doc.ranges)); - } - } - private static _compareLocations(a: vscode.Location, b: vscode.Location): number { if (a.uri.toString() < b.uri.toString()) { return -1; @@ -94,13 +78,23 @@ export default class ContentProvider implements vscode.TextDocumentContentProvid return a.range.start.compareTo(b.range.start) } } + + provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentLink[] { + // While building the virtual document we have already created the links. + // Those are composed from the range inside the document and a target uri + // to which they point + const doc = this._documents.get(document.uri.toString()); + if (doc) { + return doc.links; + } + } } let seq = 0; export function encodeLocation(uri: vscode.Uri, pos: vscode.Position): vscode.Uri { const query = JSON.stringify([uri.toString(), pos.line, pos.character]); - return vscode.Uri.parse(`${ContentProvider.scheme}:References.locations?${query}#${seq++}`); + return vscode.Uri.parse(`${Provider.scheme}:References.locations?${query}#${seq++}`); } export function decodeLocation(uri: vscode.Uri): [vscode.Uri, vscode.Position] { diff --git a/contentprovider-sample/src/referencesDocument.ts b/contentprovider-sample/src/referencesDocument.ts index f3429ad2..a5b505b2 100644 --- a/contentprovider-sample/src/referencesDocument.ts +++ b/contentprovider-sample/src/referencesDocument.ts @@ -12,7 +12,7 @@ export default class ReferencesDocument { private _locations: vscode.Location[]; private _lines: string[]; - private _ranges: vscode.Range[]; + private _links: vscode.DocumentLink[]; private _join: Thenable; constructor(uri: vscode.Uri, locations: vscode.Location[], emitter: vscode.EventEmitter) { @@ -25,7 +25,7 @@ export default class ReferencesDocument { // Start with printing a header and start resolving this._lines = [`Found ${this._locations.length} references`]; - this._ranges = []; + this._links = []; this._join = this._populate(); } @@ -33,8 +33,8 @@ export default class ReferencesDocument { return this._lines.join('\n'); } - get ranges() { - return this._ranges; + get links() { + return this._links; } join(): Thenable { @@ -98,7 +98,7 @@ export default class ReferencesDocument { for (let i = 0; i < ranges.length; i++) { const {start: {line}} = ranges[i]; this._appendLeading(doc, line, ranges[i - 1]); - this._appendMatch(doc, line, ranges[i]); + this._appendMatch(doc, line, ranges[i], uri); this._appendTrailing(doc, line, ranges[i + 1]); } @@ -115,17 +115,18 @@ export default class ReferencesDocument { } } - private _appendMatch(doc: vscode.TextDocument, line:number, match: vscode.Range) { + private _appendMatch(doc: vscode.TextDocument, line:number, match: vscode.Range, target: vscode.Uri) { const text = doc.lineAt(line).text; const preamble = ` ${line + 1}: `; // Append line, use new length of lines-array as line number - // for decoration in the document (should really be a link) + // for a link that point to the reference const len = this._lines.push(preamble + text); - this._ranges.push(new vscode.Range( - len - 1, preamble.length + match.start.character, - len - 1, preamble.length + match.end.character) - ); + + // Create a document link that will reveal the reference + const linkRange = new vscode.Range(len - 1, preamble.length + match.start.character, len - 1, preamble.length + match.end.character); + const linkTarget = target.with({ fragment: String(1 + match.start.line) }); + this._links.push(new vscode.DocumentLink(linkRange, linkTarget)); } private _appendTrailing(doc: vscode.TextDocument, line: number, next: vscode.Range): void { diff --git a/previewhtml-sample/README.md b/previewhtml-sample/README.md index f87c2b89..afcfba59 100644 --- a/previewhtml-sample/README.md +++ b/previewhtml-sample/README.md @@ -9,7 +9,7 @@ The purpose of the extension is to show a preview of the properties in the decla - Use `Show CSS Properties Preview` - Position the cursor inside the declaration block of the rule - The properties are rendered in the preview -- Edit the propertis and the preview is updated +- Edit the properties and the preview is updated ![Navigation](images/preview.gif)