提交 770ede15 编写于 作者: J Johannes Rieken

One code lens provider is one code lens support, fixes #360

上级 bd93ccb8
......@@ -22,12 +22,8 @@ import {IModelService} from 'vs/editor/common/services/modelService';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService';
import {Range} from 'vs/editor/common/core/range';
import {CodeLensRegistry} from '../common/codelens';
import {CodeLensRegistry, ICodeLensData, getCodeLensData} from '../common/codelens';
interface ICodeLensData {
symbol: Modes.ICodeLensSymbol;
support: Modes.ICodeLensSupport;
}
class CodeLensViewZone implements EditorBrowser.IViewZone {
......@@ -428,22 +424,8 @@ export class CodeLensContribution implements EditorCommon.IEditorContribution {
this._currentFindCodeLensSymbolsPromise.cancel();
}
let resource = model.getAssociatedResource();
let symbols: ICodeLensData[] = [];
let promises = CodeLensRegistry.all(model).map(support => {
return support.findCodeLensSymbols(resource).then(result => {
if (!Array.isArray(result)) {
return;
}
for (let symbol of result) {
symbols.push({ symbol, support });
}
}, err => {
errors.onUnexpectedError(err);
});
});
this._currentFindCodeLensSymbolsPromise = TPromise.join(promises).then(() => symbols);
this._currentFindCodeLensSymbolsPromise = getCodeLensData(model.getAssociatedResource(),
model.getModeId());
var counterValue = ++this._modelChangeCounter;
this._currentFindCodeLensSymbolsPromise.then((result) => {
......
......@@ -5,6 +5,7 @@
'use strict';
import {onUnexpectedError} from 'vs/base/common/errors';
import URI from 'vs/base/common/uri';
import {IAction, Action} from 'vs/base/common/actions';
import {TPromise} from 'vs/base/common/winjs.base';
......@@ -13,6 +14,28 @@ import {Range} from 'vs/editor/common/core/range';
import {ICodeLensSupport, ICodeLensSymbol, ICommand} from 'vs/editor/common/modes';
import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry';
const _registry = new LanguageFeatureRegistry<ICodeLensSupport>('codeLensSupport');
export const CodeLensRegistry = new LanguageFeatureRegistry<ICodeLensSupport>('codeLensSupport');
export {_registry as CodeLensRegistry}
export interface ICodeLensData {
symbol: ICodeLensSymbol;
support: ICodeLensSupport;
}
export function getCodeLensData(resource: URI, modeId: string) {
const symbols: ICodeLensData[] = [];
const promises = CodeLensRegistry.all({ uri: resource, language: modeId }).map(support => {
return support.findCodeLensSymbols(resource).then(result => {
if (!Array.isArray(result)) {
return;
}
for (let symbol of result) {
symbols.push({ symbol, support });
}
}, err => {
onUnexpectedError(err);
});
});
return TPromise.join(promises).then(() => symbols);
}
\ No newline at end of file
......@@ -268,7 +268,7 @@ export class PluginHostAPIImplementation {
return features.codeActions.register(selector, provider);
},
registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
return features.codeLens.register(selector, provider);
return languageFeatures.registerCodeLensProvider(selector, provider);
},
registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable {
return features.definition.register(selector, provider);
......
......@@ -57,7 +57,7 @@ function asWinJsPromise<T>(callback: (token: vscode.CancellationToken) => T | Th
// --- adapter
class OutlineSupportAdapter implements IOutlineSupport {
class OutlineAdapter implements IOutlineSupport {
private _documents: PluginHostModelService;
private _provider: vscode.DocumentSymbolProvider;
......@@ -71,7 +71,7 @@ class OutlineSupportAdapter implements IOutlineSupport {
let doc = this._documents.getDocument(resource);
return asWinJsPromise(token => this._provider.provideDocumentSymbols(doc, token)).then(value => {
if (Array.isArray(value)) {
return value.map(OutlineSupportAdapter._convertSymbolInfo);
return value.map(OutlineAdapter._convertSymbolInfo);
}
});
}
......@@ -87,7 +87,78 @@ class OutlineSupportAdapter implements IOutlineSupport {
}
}
type Adapter = OutlineSupportAdapter;
class CodeLensAdapter implements modes.ICodeLensSupport {
private _documents: PluginHostModelService;
private _provider: vscode.CodeLensProvider;
private _cache: { [uri: string]: vscode.CodeLens[] } = Object.create(null);
constructor(documents: PluginHostModelService, provider: vscode.CodeLensProvider) {
this._documents = documents;
this._provider = provider;
}
findCodeLensSymbols(resource: URI): TPromise<modes.ICodeLensSymbol[]> {
let doc = this._documents.getDocument(resource);
let key = resource.toString();
delete this._cache[key];
return asWinJsPromise(token => this._provider.provideCodeLenses(doc, token)).then(value => {
if (!Array.isArray(value)) {
return;
}
this._cache[key] = value;
return value.map((lens, i) => {
return <modes.ICodeLensSymbol>{
id: String(i),
range: TypeConverters.fromRange(lens.range)
}
});
});
}
resolveCodeLensSymbol(resource: URI, symbol: modes.ICodeLensSymbol): TPromise<modes.ICommand> {
let lenses = this._cache[resource.toString()];
if (!lenses) {
return;
}
let lens = lenses[Number(symbol.id)];
if (!lens) {
return;
}
let resolve: TPromise<vscode.CodeLens>;
if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) {
resolve = TPromise.as(lens);
} else {
resolve = asWinJsPromise(token => this._provider.resolveCodeLens(lens, token));
}
return resolve.then(newLens => {
lens = newLens || lens;
let command = lens.command;
if (!command) {
command = {
title: '<<MISSING COMMAND>>',
command: 'missing',
}
}
return {
id: command.command,
title: command.title,
arguments: command.arguments
}
});
}
}
type Adapter = OutlineAdapter | CodeLensAdapter;
@Remotable.PluginHostContext('ExtHostLanguageFeatures')
export class ExtHostLanguageFeatures {
......@@ -110,22 +181,59 @@ export class ExtHostLanguageFeatures {
});
}
private _nextHandle(): number {
return ExtHostLanguageFeatures._handlePool++;
}
private _noAdapter() {
return TPromise.wrapError(new Error('no adapter found'));
}
// --- outline
registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable {
const handle = ExtHostLanguageFeatures._handlePool++;
this._adapter[handle] = new OutlineSupportAdapter(this._documents, provider);
const handle = this._nextHandle();
this._adapter[handle] = new OutlineAdapter(this._documents, provider);
this._proxy.$registerOutlineSupport(handle, selector);
return this._createDisposable(handle);
}
$getOutline(handle: number, resource: URI): TPromise<IOutlineEntry[]>{
let adapter = this._adapter[handle];
if (adapter instanceof OutlineSupportAdapter) {
if (adapter instanceof OutlineAdapter) {
return adapter.getOutline(resource);
} else {
return this._noAdapter();
}
return TPromise.wrapError(new Error('no adapter found'));
}
// --- code lens
registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
const handle = this._nextHandle();
this._adapter[handle] = new CodeLensAdapter(this._documents, provider);
this._proxy.$registerCodeLensSupport(handle, selector);
return this._createDisposable(handle);
}
$findCodeLensSymbols(handle:number, resource: URI): TPromise<modes.ICodeLensSymbol[]> {
let adapter = this._adapter[handle];
if (adapter instanceof CodeLensAdapter) {
return adapter.findCodeLensSymbols(resource);
} else {
return this._noAdapter();
}
}
$resolveCodeLensSymbol(handle:number, resource: URI, symbol: modes.ICodeLensSymbol): TPromise<modes.ICommand> {
let adapter = this._adapter[handle];
if (adapter instanceof CodeLensAdapter) {
return adapter.resolveCodeLensSymbol(resource, symbol);
} else {
return this._noAdapter();
}
}
}
@Remotable.MainContext('MainThreadLanguageFeatures')
......@@ -150,12 +258,25 @@ export class MainThreadLanguageFeatures {
// --- outline
$registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
let disposable = OutlineRegistry.register(selector, <IOutlineSupport>{
this._registrations[handle] = OutlineRegistry.register(selector, <IOutlineSupport>{
getOutline: (resource: URI): TPromise<IOutlineEntry[]> => {
return this._proxy.$getOutline(handle, resource);
}
});
this._registrations[handle] = disposable;
return undefined;
}
// --- code lens
$registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
this._registrations[handle] = CodeLensRegistry.register(selector, <modes.ICodeLensSupport>{
findCodeLensSymbols: (resource: URI): TPromise<modes.ICodeLensSymbol[]> => {
return this._proxy.$findCodeLensSymbols(handle, resource);
},
resolveCodeLensSymbol: (resource: URI, symbol: modes.ICodeLensSymbol): TPromise<modes.ICommand> => {
return this._proxy.$resolveCodeLensSymbol(handle, resource, symbol);
}
});
return undefined;
}
}
\ No newline at end of file
......@@ -480,78 +480,6 @@ export class MainThreadCodeActions extends AbstractMainThreadFeature<modes.IQuic
}
}
// ---- OutlineSupport aka DocumentSymbols
export class ExtensionHostDocumentSymbols extends AbstractExtensionHostFeature<vscode.DocumentSymbolProvider, MainThreadDocumentSymbols> {
constructor( @IThreadService threadService: IThreadService) {
super(threadService.getRemotable(MainThreadDocumentSymbols), threadService);
}
protected _runAsCommand(resource: URI): TPromise<IOutlineEntry[]> {
if (!(resource instanceof URI)) {
return TPromise.wrapError('uri missing');
}
let symbols: vscode.SymbolInformation[] = [];
let document = this._models.getDocument(resource);
let candidate = {
language: document.languageId,
uri: document.uri
};
let promises = this._registry.all(candidate).map(provider => {
return asWinJsPromise(token => {
return provider.provideDocumentSymbols(document, token);
}).then(result => {
if (Array.isArray(result)) {
symbols.push(...result);
}
}, err => {
console.log(err);
});
});
return TPromise.join(promises).then(() => {
return symbols
.sort(ExtensionHostDocumentSymbols._compareByStart)
.map(ExtensionHostDocumentSymbols._convertSymbolInfo);
});
}
private static _compareByStart(a: vscode.SymbolInformation, b: vscode.SymbolInformation): number {
if (a.location.range.start.isBefore(b.location.range.start)) {
return -1;
} else if (b.location.range.start.isBefore(a.location.range.start)) {
return 1;
} else {
return 0;
}
}
private static _convertSymbolInfo(symbol: vscode.SymbolInformation): IOutlineEntry {
return <IOutlineEntry>{
type: TypeConverters.fromSymbolKind(symbol.kind),
range: TypeConverters.fromRange(symbol.location.range),
containerLabel: symbol.containerName,
label: symbol.name,
icon: undefined,
};
}
}
@Remotable.MainContext('MainThreadDocumentSymbols2')
export class MainThreadDocumentSymbols extends AbstractMainThreadFeature<IOutlineSupport> implements IOutlineSupport {
constructor( @IThreadService threadService: IThreadService) {
super('vscode.executeDocumentSymbolProvider', OutlineRegistry, threadService);
}
getOutline(resource: URI): TPromise<IOutlineEntry[]>{
return this._executeCommand(resource);
}
}
// -- Rename provider
export class ExtensionHostRename extends AbstractExtensionHostFeature<vscode.RenameProvider, MainThreadRename> {
......@@ -1046,114 +974,6 @@ export class MainThreadCompletions extends AbstractMainThreadFeature<modes.ISugg
}
}
// ---- Code Lens
@Remotable.PluginHostContext('ExtensionHostCodeLens')
export class ExtensionHostCodeLens extends AbstractExtensionHostFeature<vscode.CodeLensProvider, MainThreadCodeLens> {
private static _idPool = 0;
private _lenses: { [id: string]: [string, vscode.CodeLensProvider, vscode.CodeLens] } = Object.create(null);
constructor(@IThreadService threadService: IThreadService) {
super(threadService.getRemotable(MainThreadCodeLens), threadService);
this._models.onDidRemoveDocument(event => this._clearCache(event.uri));
}
_clearCache(resource: URI): void {
for (let key in this._lenses) {
if (this._lenses[key][0] === resource.toString()) {
delete this._lenses[key];
}
}
}
_runAsCommand(resource: URI): TPromise<modes.ICodeLensSymbol[]> {
let document = this._models.getDocument(resource);
let result: modes.ICodeLensSymbol[] = [];
let promises = this._getAllFor(document).map(provider => {
let providerCanResolveLens = typeof provider.resolveCodeLens === 'function';
return asWinJsPromise(token => provider.provideCodeLenses(document, token)).then(lenses => {
// new commands
this._clearCache(resource);
if (!Array.isArray(lenses)) {
return;
}
for (let lens of lenses) {
if (!providerCanResolveLens && !lens.isResolved) {
throw new Error('illegal state - code lens must be resolved or provider must implement resolveCodeLens-function');
}
let id = 'code_lense_#' + ExtensionHostCodeLens._idPool++;
this._lenses[id] = [resource.toString(), provider, lens];
result.push({
id,
range: TypeConverters.fromRange(lens.range)
});
}
}, err => {
console.error(err);
});
});
return TPromise.join(promises).then(() => {
return result;
});
}
_resolveCodeLensSymbol(symbol: modes.ICodeLensSymbol): TPromise<modes.ICommand> {
if (!this._lenses[symbol.id]) {
return;
}
let [, provider, lens] = this._lenses[symbol.id];
let resolve: TPromise<vscode.CodeLens>;
if (typeof provider.resolveCodeLens !== 'function') {
resolve = TPromise.as(lens);
} else {
resolve = asWinJsPromise(token => provider.resolveCodeLens(lens, token));
}
return resolve.then(newLens => {
lens = newLens || lens;
if (lens.command) {
return {
id: <string>lens.command.command,
title: lens.command.title,
arguments: lens.command.arguments
}
}
});
}
}
@Remotable.MainContext('MainThreadCodeLens')
export class MainThreadCodeLens extends AbstractMainThreadFeature<modes.ICodeLensSupport> implements modes.ICodeLensSupport {
private _proxy: ExtensionHostCodeLens;
constructor( @IThreadService threadService: IThreadService) {
super('vscode.executeCodeLensProvider', CodeLensRegistry, threadService);
this._proxy = threadService.getRemotable(ExtensionHostCodeLens);
}
findCodeLensSymbols(resource: URI): TPromise<modes.ICodeLensSymbol[]> {
return this._executeCommand(resource);
}
resolveCodeLensSymbol(resource: URI, symbol: modes.ICodeLensSymbol): TPromise<modes.ICommand> {
return this._proxy._resolveCodeLensSymbol(symbol);
}
}
// --- workspace symbols
export class ExtensionHostWorkspaceSymbols {
......@@ -1261,8 +1081,6 @@ export namespace LanguageFeatures {
threadService.getRemotable(MainThreadOccurrencesFeature);
threadService.getRemotable(MainThreadReferenceSearch);
threadService.getRemotable(MainThreadCodeActions);
threadService.getRemotable(MainThreadCodeLens);
// threadService.getRemotable(MainThreadDocumentSymbols);
threadService.getRemotable(MainThreadWorkspaceSymbols);
threadService.getRemotable(MainThreadRename);
threadService.getRemotable(MainThreadFormatDocument);
......@@ -1279,8 +1097,6 @@ export namespace LanguageFeatures {
documentHighlight: new ExtensionHostOccurrencesFeature(threadService),
referenceSearch: new ExtensionHostReferenceSearch(threadService),
codeActions: new ExtensionHostCodeActions(threadService),
codeLens: threadService.getRemotable(ExtensionHostCodeLens),
// documentSymbols: new ExtensionHostDocumentSymbols(threadService),
workspaceSymbols: new ExtensionHostWorkspaceSymbols(threadService),
rename: new ExtensionHostRename(threadService),
formatDocument: new ExtHostFormatDocument(threadService),
......
......@@ -509,7 +509,7 @@ export class CodeLens {
constructor(range: Range, command?: vscode.Command) {
this.range = range;
this.command;
this.command = command;
}
get isResolved(): boolean {
......
......@@ -11,7 +11,7 @@ import {create} from 'vs/base/common/types';
import URI from 'vs/base/common/uri';
import {TPromise} from 'vs/base/common/winjs.base';
import {PluginHostDocument} from 'vs/workbench/api/common/pluginHostDocuments';
import * as phTypes from 'vs/workbench/api/common/pluginHostTypes';
import * as types from 'vs/workbench/api/common/pluginHostTypes';
import {Range as CodeEditorRange} from 'vs/editor/common/core/range';
import * as EditorCommon from 'vs/editor/common/editorCommon';
import threadService from './testThreadService'
......@@ -20,6 +20,7 @@ import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pl
import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments';
import {SyncDescriptor0} from 'vs/platform/instantiation/common/descriptors';
import {OutlineRegistry, getOutlineEntries} from 'vs/editor/contrib/quickOpen/common/quickOpen';
import {CodeLensRegistry, getCodeLensData} from 'vs/editor/contrib/codelens/common/codelens';
import {LanguageSelector, ModelLike} from 'vs/editor/common/modes/languageSelector';
......@@ -76,6 +77,8 @@ suite('ExtHostLanguageFeatures', function() {
.then(() => done(), err => done(err));
});
// --- outline
test('DocumentSymbols, register/deregister', function(done) {
assert.equal(OutlineRegistry.all(model).length, 0);
let d1 = extHost.registerDocumentSymbolProvider('far', <vscode.DocumentSymbolProvider>{
......@@ -102,7 +105,7 @@ suite('ExtHostLanguageFeatures', function() {
}));
disposables.push(extHost.registerDocumentSymbolProvider('far', <vscode.DocumentSymbolProvider>{
provideDocumentSymbols(): any {
return [new phTypes.SymbolInformation('test', phTypes.SymbolKind.Field, new phTypes.Range(0, 0, 0, 0))];
return [new types.SymbolInformation('test', types.SymbolKind.Field, new types.Range(0, 0, 0, 0))];
}
}));
......@@ -120,7 +123,7 @@ suite('ExtHostLanguageFeatures', function() {
test('DocumentSymbols, data conversion', function(done) {
disposables.push(extHost.registerDocumentSymbolProvider('far', <vscode.DocumentSymbolProvider>{
provideDocumentSymbols(): any {
return [new phTypes.SymbolInformation('test', phTypes.SymbolKind.Field, new phTypes.Range(0, 0, 0, 0))];
return [new types.SymbolInformation('test', types.SymbolKind.Field, new types.Range(0, 0, 0, 0))];
}
}));
......@@ -139,4 +142,79 @@ suite('ExtHostLanguageFeatures', function() {
});
});
});
// --- code lens
test('CodeLens, evil provider', function(done) {
disposables.push(extHost.registerCodeLensProvider('far', <vscode.CodeLensProvider>{
provideCodeLenses():any {
throw new Error('evil')
}
}));
disposables.push(extHost.registerCodeLensProvider('far', <vscode.CodeLensProvider>{
provideCodeLenses() {
return [new types.CodeLens(new types.Range(0, 0, 0, 0))];
}
}));
threadService.sync().then(() => {
getCodeLensData(model.uri, model.language).then(value => {
assert.equal(value.length, 1);
done();
});
});
});
test('CodeLens, do not resolve a resolved lens', function(done) {
disposables.push(extHost.registerCodeLensProvider('far', <vscode.CodeLensProvider>{
provideCodeLenses():any {
return [new types.CodeLens(
new types.Range(0, 0, 0, 0),
{ command: 'id', title: 'Title' })];
},
resolveCodeLens():any {
assert.ok(false, 'do not resolve');
}
}));
threadService.sync().then(() => {
getCodeLensData(model.uri, model.language).then(value => {
assert.equal(value.length, 1);
let data = value[0];
data.support.resolveCodeLensSymbol(model.uri, data.symbol).then(command => {
assert.equal(command.id, 'id');
assert.equal(command.title, 'Title');
done();
});
});
});
});
test('CodeLens, missing command', function(done) {
disposables.push(extHost.registerCodeLensProvider('far', <vscode.CodeLensProvider>{
provideCodeLenses() {
return [new types.CodeLens(new types.Range(0, 0, 0, 0))];
}
}));
threadService.sync().then(() => {
getCodeLensData(model.uri, model.language).then(value => {
assert.equal(value.length, 1);
let data = value[0];
data.support.resolveCodeLensSymbol(model.uri, data.symbol).then(command => {
assert.equal(command.id, 'missing');
assert.equal(command.title, '<<MISSING COMMAND>>');
done();
});
});
});
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册