diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 45a94068f00..2f52a0d07f0 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -380,7 +380,7 @@ export interface IComputeExtraInfoResult { className?: string; } export interface IExtraInfoSupport { - computeInfo(resource:URL, position:EditorCommon.IPosition):TPromise; + computeInfo(resource:URI, position:EditorCommon.IPosition):TPromise; } @@ -402,6 +402,7 @@ export interface ISuggestion { filterText?: string; sortText?: string; noAutoAccept?: boolean; + id?: string; } /** @@ -531,7 +532,7 @@ export interface IOccurence { * Interface used to find occurrences of a symbol */ export interface IOccurrencesSupport { - findOccurrences(resource:URL, position:EditorCommon.IPosition, strict?:boolean):TPromise; + findOccurrences(resource:URI, position:EditorCommon.IPosition, strict?:boolean):TPromise; } @@ -566,7 +567,7 @@ export interface IReferenceSupport { */ export interface IDeclarationSupport { canFindDeclaration(context:ILineContext, offset:number):boolean; - findDeclaration(resource:URL, position:EditorCommon.IPosition):TPromise; + findDeclaration(resource:URI, position:EditorCommon.IPosition):TPromise; } export interface ITypeDeclarationSupport { diff --git a/src/vs/editor/contrib/codelens/browser/codelens.ts b/src/vs/editor/contrib/codelens/browser/codelens.ts index 84585d921fb..b65a862004e 100644 --- a/src/vs/editor/contrib/codelens/browser/codelens.ts +++ b/src/vs/editor/contrib/codelens/browser/codelens.ts @@ -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,7 @@ 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); var counterValue = ++this._modelChangeCounter; this._currentFindCodeLensSymbolsPromise.then((result) => { diff --git a/src/vs/editor/contrib/codelens/common/codelens.ts b/src/vs/editor/contrib/codelens/common/codelens.ts index cf84481f124..cd2da755419 100644 --- a/src/vs/editor/contrib/codelens/common/codelens.ts +++ b/src/vs/editor/contrib/codelens/common/codelens.ts @@ -5,14 +5,37 @@ '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'; -import {IRange, IPosition} from 'vs/editor/common/editorCommon'; +import {IModel, IRange, IPosition} from 'vs/editor/common/editorCommon'; 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('codeLensSupport'); +export const CodeLensRegistry = new LanguageFeatureRegistry('codeLensSupport'); -export {_registry as CodeLensRegistry} +export interface ICodeLensData { + symbol: ICodeLensSymbol; + support: ICodeLensSupport; +} + +export function getCodeLensData(model: IModel) { + + const symbols: ICodeLensData[] = []; + const promises = CodeLensRegistry.all(model).map(support => { + return support.findCodeLensSymbols(model.getAssociatedResource()).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 diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index a9e8d95ffb0..4a0068308a6 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -34,35 +34,7 @@ import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IEditorService} from 'vs/platform/editor/common/editor'; import {FindReferencesController} from 'vs/editor/contrib/referenceSearch/browser/referenceSearch'; import {IKeybindingService, IKeybindingContextKey} from 'vs/platform/keybinding/common/keybindingService'; -import FeatureRegistry from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; - -function getDeclarationsAtPosition(editor: EditorCommon.ICommonCodeEditor, position = editor.getPosition()): TPromise { - - let references: Modes.IReference[] = []; - let promises: TPromise[] = []; - - let model = editor.getModel(); - - for (let provider of FeatureRegistry.all(model)) { - - let promise = provider.findDeclaration(model.getAssociatedResource(), - position); - - promises.push(promise.then(result => { - if (Array.isArray(result)) { - references.push(...result); - } else { - references.push(result); - } - }, err => { - Errors.onUnexpectedError(err); - })); - } - - return TPromise.join(promises).then(() => { - return coalesce(references); - }); -} +import {DeclarationRegistry, getDeclarationsAtPosition} from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; export abstract class GoToTypeAction extends EditorAction { @@ -182,7 +154,7 @@ export class GoToDeclarationAction extends GoToTypeAction { } public isSupported(): boolean { - return FeatureRegistry.has(this.editor.getModel()) && super.isSupported(); + return DeclarationRegistry.has(this.editor.getModel()) && super.isSupported(); } public getEnablementState():boolean { @@ -193,7 +165,7 @@ export class GoToDeclarationAction extends GoToTypeAction { var model = this.editor.getModel(), position = this.editor.getSelection().getStartPosition(); - return FeatureRegistry.all(model).some(provider => { + return DeclarationRegistry.all(model).some(provider => { return provider.canFindDeclaration( model.getLineContext(position.lineNumber), position.column - 1); @@ -201,7 +173,7 @@ export class GoToDeclarationAction extends GoToTypeAction { } protected _resolve(resource: URI, position: EditorCommon.IPosition): TPromise { - return getDeclarationsAtPosition(this.editor); + return getDeclarationsAtPosition(this.editor.getModel(), this.editor.getPosition()); } } @@ -456,7 +428,7 @@ class GotoDefinitionWithMouseEditorContribution implements EditorCommon.IEditorC (Browser.isIE11orEarlier || mouseEvent.event.detail <= 1) && // IE does not support event.detail properly mouseEvent.target.type === EditorCommon.MouseTargetType.CONTENT_TEXT && (mouseEvent.event[GotoDefinitionWithMouseEditorContribution.TRIGGER_MODIFIER] || (withKey && withKey.keyCode === GotoDefinitionWithMouseEditorContribution.TRIGGER_KEY_VALUE)) && - FeatureRegistry.has(this.editor.getModel()); + DeclarationRegistry.has(this.editor.getModel()); } private findDefinition(target:EditorBrowser.IMouseTarget):TPromise { @@ -465,7 +437,7 @@ class GotoDefinitionWithMouseEditorContribution implements EditorCommon.IEditorC return TPromise.as(null); } - return getDeclarationsAtPosition(this.editor, target.position); + return getDeclarationsAtPosition(this.editor.getModel(), target.position); } private gotoDefinition(target:EditorBrowser.IMouseTarget, sideBySide:boolean):TPromise { diff --git a/src/vs/editor/contrib/goToDeclaration/common/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/common/goToDeclaration.ts index e3226572277..2af92d98124 100644 --- a/src/vs/editor/contrib/goToDeclaration/common/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/common/goToDeclaration.ts @@ -5,9 +5,39 @@ 'use strict'; +import URI from 'vs/base/common/uri'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {onUnexpectedError} from 'vs/base/common/errors'; +import {IModel, IPosition} from 'vs/editor/common/editorCommon'; import {IDeclarationSupport} from 'vs/editor/common/modes'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; +import {IReference} from 'vs/editor/common/modes'; -const FeatureRegistry = new LanguageFeatureRegistry('declarationSupport'); +export const DeclarationRegistry = new LanguageFeatureRegistry('declarationSupport'); -export default FeatureRegistry; \ No newline at end of file +export function getDeclarationsAtPosition(model: IModel, position: IPosition): TPromise { + + const resource = model.getAssociatedResource(); + const provider = DeclarationRegistry.ordered(model); + + // get results + const promises = provider.map((provider, idx) => { + return provider.findDeclaration(resource, position).then(result => { + return result; + }, err => { + onUnexpectedError(err); + }); + }); + + return TPromise.join(promises).then(allReferences => { + let result: IReference[] = []; + for (let references of allReferences) { + if (Array.isArray(references)) { + result.push(...references); + } else if (references) { + result.push(references); + } + } + return result; + }); +} \ No newline at end of file diff --git a/src/vs/editor/contrib/hover/browser/modesContentHover.ts b/src/vs/editor/contrib/hover/browser/modesContentHover.ts index 4fbfae8608b..e0e92c72331 100644 --- a/src/vs/editor/contrib/hover/browser/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/browser/modesContentHover.ts @@ -15,8 +15,8 @@ import HoverWidget = require('./hoverWidgets'); import HtmlContent = require('vs/base/common/htmlContent'); import {renderHtml} from 'vs/base/browser/htmlContentRenderer'; import {tokenizeToHtmlContent} from 'vs/editor/common/modes/textToHtmlTokenizer'; -import ExtraInfoRegistry from '../common/hover'; import {Range} from 'vs/editor/common/core/range'; +import {ExtraInfoRegistry, getExtraInfoAtPosition} from '../common/hover'; class ModesContentComputer implements HoverOperation.IHoverComputer { @@ -46,27 +46,10 @@ class ModesContentComputer implements HoverOperation.IHoverComputer[] = []; - - for (let support of supports) { - promises.push(support.computeInfo(this._editor.getModel().getAssociatedResource(), { - lineNumber: this._range.startLineNumber, - column: this._range.startColumn, - }).then((result: Modes.IComputeExtraInfoResult) => { - if (result) { - let hasRange = (typeof result.range !== 'undefined'); - let hasValue = (typeof result.value !== 'undefined'); - let hasHtmlContent = (typeof result.htmlContent !== 'undefined' && result.htmlContent && result.htmlContent.length > 0); - if (hasRange && (hasValue || hasHtmlContent)) { - values.push(result); - } - } - })); - } - - return TPromise.join(promises).then(() => values); + return getExtraInfoAtPosition(model, { + lineNumber: this._range.startLineNumber, + column: this._range.startColumn + }); } public computeSync(): Modes.IComputeExtraInfoResult[] { diff --git a/src/vs/editor/contrib/hover/common/hover.ts b/src/vs/editor/contrib/hover/common/hover.ts index a811c6dac77..0ffc441ae9c 100644 --- a/src/vs/editor/contrib/hover/common/hover.ts +++ b/src/vs/editor/contrib/hover/common/hover.ts @@ -5,9 +5,36 @@ 'use strict'; -import {IExtraInfoSupport} from 'vs/editor/common/modes'; +import URI from 'vs/base/common/uri'; +import {IExtraInfoSupport, IComputeExtraInfoResult} from 'vs/editor/common/modes'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {coalesce} from 'vs/base/common/arrays'; +import {onUnexpectedError} from 'vs/base/common/errors'; +import {IPosition, IModel} from 'vs/editor/common/editorCommon'; -const ExtraInfoRegistry = new LanguageFeatureRegistry('extraInfoSupport'); +export const ExtraInfoRegistry = new LanguageFeatureRegistry('extraInfoSupport'); -export default ExtraInfoRegistry; \ No newline at end of file +export function getExtraInfoAtPosition(model: IModel, position: IPosition): TPromise { + + const resource = model.getAssociatedResource(); + const supports = ExtraInfoRegistry.ordered(model); + const values: IComputeExtraInfoResult[] = []; + + const promises = supports.map((support, idx) => { + return support.computeInfo(resource, position).then(result => { + if (result) { + let hasRange = (typeof result.range !== 'undefined'); + let hasValue = (typeof result.value !== 'undefined'); + let hasHtmlContent = (typeof result.htmlContent !== 'undefined' && result.htmlContent && result.htmlContent.length > 0); + if (hasRange && (hasValue || hasHtmlContent)) { + values[idx] = result; + } + } + }, err => { + onUnexpectedError(err); + }); + }); + + return TPromise.join(promises).then(() => coalesce(values)); +} \ No newline at end of file diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsModel.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsModel.ts index 9dee0382288..d5a645759eb 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsModel.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsModel.ts @@ -11,7 +11,7 @@ import async = require('vs/base/common/async'); import events = require('vs/base/common/eventEmitter'); import EditorCommon = require('vs/editor/common/editorCommon'); import Modes = require('vs/editor/common/modes'); -import {ParameterHintsRegistry} from '../common/parameterHints'; +import {ParameterHintsRegistry, getParameterHints} from '../common/parameterHints'; import {sequence} from 'vs/base/common/async'; function hashParameterHints(hints: Modes.IParameterHints): string { @@ -96,15 +96,8 @@ export class ParameterHintsModel extends events.EventEmitter { } public doTrigger(triggerCharacter: string): TPromise { - let model = this.editor.getModel(); - let support = ParameterHintsRegistry.ordered(model)[0]; - if (!support) { - return TPromise.as(false); - } - - return support.getParameterHints(model.getAssociatedResource(), this.editor.getPosition(), triggerCharacter).then((result: Modes.IParameterHints) => { + return getParameterHints(this.editor.getModel(), this.editor.getPosition(), triggerCharacter).then(result => { var hash = hashParameterHints(result); - if (!result || result.signatures.length === 0 || (this.hash && hash !== this.hash)) { this.cancel(); this.emit('cancel'); diff --git a/src/vs/editor/contrib/parameterHints/common/parameterHints.ts b/src/vs/editor/contrib/parameterHints/common/parameterHints.ts index 47b974a63f3..58b4bd073bc 100644 --- a/src/vs/editor/contrib/parameterHints/common/parameterHints.ts +++ b/src/vs/editor/contrib/parameterHints/common/parameterHints.ts @@ -5,7 +5,20 @@ 'use strict'; -import {IParameterHintsSupport} from 'vs/editor/common/modes'; +import {IParameterHintsSupport, IParameterHints} from 'vs/editor/common/modes'; +import {IModel, IPosition} from 'vs/editor/common/editorCommon'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {onUnexpectedError} from 'vs/base/common/errors'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; export const ParameterHintsRegistry = new LanguageFeatureRegistry('parameterHintsSupport'); + +export function getParameterHints(model:IModel, position:IPosition, triggerCharacter: string): TPromise { + + let support = ParameterHintsRegistry.ordered(model)[0]; + if (!support) { + return TPromise.as(undefined); + } + + return support.getParameterHints(model.getAssociatedResource(), position, triggerCharacter); +} \ No newline at end of file diff --git a/src/vs/editor/contrib/quickFix/browser/quickFix.ts b/src/vs/editor/contrib/quickFix/browser/quickFix.ts index 3f708ee8392..160d467807f 100644 --- a/src/vs/editor/contrib/quickFix/browser/quickFix.ts +++ b/src/vs/editor/contrib/quickFix/browser/quickFix.ts @@ -24,7 +24,7 @@ import {IEventService} from 'vs/platform/event/common/event'; import {IEditorService} from 'vs/platform/editor/common/editor'; import {IMessageService} from 'vs/platform/message/common/message'; import {bulkEdit} from 'vs/editor/common/services/bulkEdit'; -import QuickFixRegistry, {IQuickFix2} from '../common/quickFix'; +import {QuickFixRegistry, IQuickFix2} from '../common/quickFix'; import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; export class QuickFixController implements EditorCommon.IEditorContribution { diff --git a/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts b/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts index f7fdff0c7eb..29677542a14 100644 --- a/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts +++ b/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts @@ -16,7 +16,7 @@ import schedulers = require('vs/base/common/async'); import errors = require('vs/base/common/errors'); import {Range} from 'vs/editor/common/core/range'; import {IMarkerService, IMarker} from 'vs/platform/markers/common/markers'; -import QuickFixRegistry, {IQuickFix2} from '../common/quickFix'; +import {QuickFixRegistry, IQuickFix2, getQuickFixes} from '../common/quickFix'; import LightBulpWidget = require('./lightBulpWidget'); enum QuickFixSuggestState { @@ -212,27 +212,7 @@ export class QuickFixModel extends events.EventEmitter { } this.quickFixRequestPromiseRange = range; - let quickFixes: IQuickFix2[] = []; - let promises = QuickFixRegistry.all(model).map(support => { - return support.getQuickFixes(model.getAssociatedResource(), range).then(result => { - if (!Array.isArray(result)) { - return - } - for (let fix of result) { - quickFixes.push({ - id: fix.id, - label: fix.label, - documentation: fix.documentation, - score: fix.score, - support - }); - } - }, err => { - errors.onUnexpectedError(err); - }); - }); - - this.quickFixRequestPromise = TPromise.join(promises).then(() => quickFixes); + this.quickFixRequestPromise = getQuickFixes(model, range); return this.quickFixRequestPromise; } diff --git a/src/vs/editor/contrib/quickFix/common/quickFix.ts b/src/vs/editor/contrib/quickFix/common/quickFix.ts index 3b17a46819b..4e7e841c8ae 100644 --- a/src/vs/editor/contrib/quickFix/common/quickFix.ts +++ b/src/vs/editor/contrib/quickFix/common/quickFix.ts @@ -5,13 +5,39 @@ 'use strict'; +import {IModel, IRange} from 'vs/editor/common/editorCommon'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {onUnexpectedError} from 'vs/base/common/errors'; import {IQuickFixSupport, IQuickFix} from 'vs/editor/common/modes'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; -const QuickFixRegistry = new LanguageFeatureRegistry('quickFixSupport'); - -export default QuickFixRegistry; +export const QuickFixRegistry = new LanguageFeatureRegistry('quickFixSupport'); export interface IQuickFix2 extends IQuickFix { support: IQuickFixSupport; +} + +export function getQuickFixes(model: IModel, range: IRange): TPromise { + + const quickFixes: IQuickFix2[] = []; + const promises = QuickFixRegistry.all(model).map(support => { + return support.getQuickFixes(model.getAssociatedResource(), range).then(result => { + if (!Array.isArray(result)) { + return + } + for (let fix of result) { + quickFixes.push({ + id: fix.id, + label: fix.label, + documentation: fix.documentation, + score: fix.score, + support + }); + } + }, err => { + onUnexpectedError(err); + }); + }); + + return TPromise.join(promises).then(() => quickFixes); } \ No newline at end of file diff --git a/src/vs/editor/contrib/quickOpen/common/quickOpen.ts b/src/vs/editor/contrib/quickOpen/common/quickOpen.ts index cf5c24c299e..4b135a963da 100644 --- a/src/vs/editor/contrib/quickOpen/common/quickOpen.ts +++ b/src/vs/editor/contrib/quickOpen/common/quickOpen.ts @@ -5,14 +5,72 @@ 'use strict'; +import {onUnexpectedError} from 'vs/base/common/errors'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {Range} from 'vs/editor/common/core/range'; +import {IModel} from 'vs/editor/common/editorCommon'; import {IOutlineEntry, IOutlineSupport} from 'vs/editor/common/modes'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; -const QuickOutineRegistry = new LanguageFeatureRegistry('outlineSupport'); +const OutlineRegistry = new LanguageFeatureRegistry('outlineSupport'); export { + OutlineRegistry, IOutlineEntry, IOutlineSupport } -export default QuickOutineRegistry; \ No newline at end of file +export function getOutlineEntries(model: IModel): TPromise<{ entries: IOutlineEntry[], outlineGroupLabel: { [n: string]: string;} }> { + + let groupLabels: { [n: string]: string } = Object.create(null); + let entries: IOutlineEntry[] = []; + + let promises = OutlineRegistry.all(model).map(support => { + + if (support.outlineGroupLabel) { + for (var key in support.outlineGroupLabel) { + if (Object.prototype.hasOwnProperty.call(support.outlineGroupLabel, key)) { + groupLabels[key] = support.outlineGroupLabel[key]; + } + } + } + + return support.getOutline(model.getAssociatedResource()).then(result => { + if (Array.isArray(result)) { + entries.push(...result); + } + }, err => { + onUnexpectedError(err); + }); + }); + + return TPromise.join(promises).then(() => { + let flatEntries: IOutlineEntry[] = []; + flatten(flatEntries, entries, ''); + flatEntries.sort(compareEntriesUsingStart); + + return { + entries: flatEntries, + outlineGroupLabel: groupLabels + } + }); +} + +function compareEntriesUsingStart(a: IOutlineEntry, b: IOutlineEntry): number{ + return Range.compareRangesUsingStarts(a.range, b.range); +} + +function flatten(bucket: IOutlineEntry[], entries: IOutlineEntry[], overrideContainerLabel: string): void { + for (let entry of entries) { + bucket.push({ + type: entry.type, + range: entry.range, + label: entry.label, + icon: entry.icon, + containerLabel: entry.containerLabel || overrideContainerLabel + }); + if (entry.children) { + flatten(bucket, entry.children, entry.label); + } + } +} diff --git a/src/vs/editor/contrib/referenceSearch/browser/referenceSearch.ts b/src/vs/editor/contrib/referenceSearch/browser/referenceSearch.ts index eff53f50596..4f0846b52f8 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referenceSearch.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referenceSearch.ts @@ -30,7 +30,7 @@ import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IKeybindingService, IKeybindingContextKey, ICommandHandler} from 'vs/platform/keybinding/common/keybindingService'; import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry'; import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; -import ReferenceSearchRegistry from '../common/referenceSearch'; +import {ReferenceRegistry, findReferences} from '../common/referenceSearch'; import IPeekViewService = peekView.IPeekViewService; export class FindReferencesController implements EditorCommon.IEditorContribution { @@ -265,7 +265,7 @@ export class ReferenceAction extends EditorAction { } public isSupported():boolean { - return ReferenceSearchRegistry.has(this.editor.getModel()) && super.isSupported(); + return ReferenceRegistry.has(this.editor.getModel()) && super.isSupported(); } public getEnablementState():boolean { @@ -278,7 +278,7 @@ export class ReferenceAction extends EditorAction { let context = model.getLineContext(position.lineNumber); let offset = position.column - 1; - return ReferenceSearchRegistry.all(model).some(support => { + return ReferenceRegistry.all(model).some(support => { return support.canFindReferences(context, offset); }); } @@ -292,22 +292,6 @@ export class ReferenceAction extends EditorAction { } } -function findReferences(model: EditorCommon.IModel, position: EditorCommon.IPosition): TPromise { - let references: Modes.IReference[] = []; - - // collect references from all providers - let promises = ReferenceSearchRegistry.all(model).map(provider => { - return provider.findReferences(model.getAssociatedResource(), position, true).then(result => { - if (Array.isArray(result)) { - references.push(...result); - } - }, err => { - errors.onUnexpectedError(err); - }); - }); - - return TPromise.join(promises).then(() => references); -} let findReferencesCommand: ICommandHandler = (accessor, args) => { diff --git a/src/vs/editor/contrib/referenceSearch/common/referenceSearch.ts b/src/vs/editor/contrib/referenceSearch/common/referenceSearch.ts index d578fcbe0cc..df94dd6e558 100644 --- a/src/vs/editor/contrib/referenceSearch/common/referenceSearch.ts +++ b/src/vs/editor/contrib/referenceSearch/common/referenceSearch.ts @@ -5,9 +5,35 @@ 'use strict'; -import {IReferenceSupport} from 'vs/editor/common/modes'; +import {IReferenceSupport, IReference} from 'vs/editor/common/modes'; +import {IModel, IPosition} from 'vs/editor/common/editorCommon'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {onUnexpectedError} from 'vs/base/common/errors'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; -const ReferenceSearchRegistry = new LanguageFeatureRegistry('referenceSupport'); +export const ReferenceRegistry = new LanguageFeatureRegistry('referenceSupport'); -export default ReferenceSearchRegistry; \ No newline at end of file +export function findReferences(model: IModel, position: IPosition): TPromise { + + + // collect references from all providers + const promises = ReferenceRegistry.ordered(model).map(provider => { + return provider.findReferences(model.getAssociatedResource(), position, true).then(result => { + if (Array.isArray(result)) { + return result; + } + }, err => { + onUnexpectedError(err); + }); + }); + + return TPromise.join(promises).then(references => { + let result: IReference[] = []; + for (let ref of references) { + if (ref) { + result.push(...ref); + } + } + return result; + }); +} diff --git a/src/vs/editor/contrib/rename/browser/rename2.ts b/src/vs/editor/contrib/rename/browser/rename2.ts index 95be4ac6cb4..e6a3abc2e6e 100644 --- a/src/vs/editor/contrib/rename/browser/rename2.ts +++ b/src/vs/editor/contrib/rename/browser/rename2.ts @@ -25,7 +25,7 @@ import {IKeybindingService, IKeybindingContextKey} from 'vs/platform/keybinding/ import {IEventService} from 'vs/platform/event/common/event'; import {IEditorService} from 'vs/platform/editor/common/editor'; import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; -import {RenameRegistry} from '../common/rename'; +import {RenameRegistry, rename} from '../common/rename'; export class RenameAction extends EditorAction { @@ -145,42 +145,12 @@ export class RenameAction extends EditorAction { // start recording of file changes so that we can figure out if a file that // is to be renamed conflicts with another (concurrent) modification - let sourceModel = this.editor.getModel().getAssociatedResource(); - let sourceSelections = this.editor.getSelections(); + let edit = createBulkEdit(this._eventService, this._editorService, this.editor); - let supports = RenameRegistry.ordered(this.editor.getModel()); - let hasResult = false; - let rejects: string[] = []; - - let factory = supports.map(support => { - return () => { - if (!hasResult) { - return support.rename(sourceModel, this.editor.getPosition(), newName).then(result => { - if (!result) { - // ignore - } else if (!result.rejectReason) { - hasResult = true; - return result; - } else { - rejects.push(result.rejectReason); - } - }); - } - }; - }); - - let edit = createBulkEdit(this._eventService, this._editorService, - this.editor); - - return sequence(factory).then(values => { - - let result = values[0]; - if (rejects.length > 0) { - return TPromise.wrapError(rejects.join('\n')); - } else if (!result) { - return TPromise.wrapError(nls.localize('no result', "No result.")); + return rename(this.editor.getModel(), this.editor.getPosition(), newName).then(result => { + if (result.rejectReason) { + return TPromise.wrapError(result.rejectReason); } - edit.add(result.edits); return edit; }); diff --git a/src/vs/editor/contrib/rename/common/rename.ts b/src/vs/editor/contrib/rename/common/rename.ts index a511e319128..a7e122a87a9 100644 --- a/src/vs/editor/contrib/rename/common/rename.ts +++ b/src/vs/editor/contrib/rename/common/rename.ts @@ -5,7 +5,56 @@ 'use strict'; -import {IRenameSupport} from 'vs/editor/common/modes'; +import {IRenameSupport, IRenameResult} from 'vs/editor/common/modes'; +import {IModel, IPosition} from 'vs/editor/common/editorCommon'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {localize} from 'vs/nls'; +import {sequence} from 'vs/base/common/async'; +import {onUnexpectedError} from 'vs/base/common/errors'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; export const RenameRegistry = new LanguageFeatureRegistry('renameSupport'); + +export function rename(model: IModel, position: IPosition, newName: string): TPromise { + + const supports = RenameRegistry.ordered(model); + const resource = model.getAssociatedResource(); + const rejects: string[] = []; + let hasResult = false; + + const factory = supports.map(support => { + return () => { + if (!hasResult) { + return support.rename(resource, position, newName).then(result => { + if (!result) { + // ignore + } else if (!result.rejectReason) { + hasResult = true; + return result; + } else { + rejects.push(result.rejectReason); + } + }); + } + }; + }); + + return sequence(factory).then(values => { + let result = values[0]; + if (rejects.length > 0) { + return { + currentName: undefined, + edits: undefined, + rejectReason: rejects.join('\n') + }; + } else if (!result) { + return { + currentName: undefined, + edits: undefined, + rejectReason: localize('no result', "No result.") + }; + } else { + return result; + } + }); +} \ No newline at end of file diff --git a/src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts index 3b66e3ef597..5d817a7ad28 100644 --- a/src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts @@ -5,6 +5,7 @@ 'use strict'; import {TPromise} from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; import * as EditorCommon from 'vs/editor/common/editorCommon'; import {IOccurrencesSupport, IOccurence} from 'vs/editor/common/modes'; import {CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; @@ -14,8 +15,34 @@ import {INullService} from 'vs/platform/instantiation/common/instantiation'; import {sequence} from 'vs/base/common/async'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; -const DocumentHighlighterRegistry = new LanguageFeatureRegistry('occurrencesSupport'); -export default DocumentHighlighterRegistry; +export const OccurrencesRegistry = new LanguageFeatureRegistry('occurrencesSupport'); + +export function getOccurrencesAtPosition(model: EditorCommon.IModel, position: EditorCommon.IPosition):TPromise { + + const resource = model.getAssociatedResource(); + const orderedByScore = OccurrencesRegistry.ordered(model); + let foundResult = false; + + // in order of score ask the occurrences provider + // until someone response with a good result + // (good = none empty array) + return sequence(orderedByScore.map(provider => { + return () => { + if (!foundResult) { + return provider.findOccurrences(resource, position).then(data => { + if (Array.isArray(data) && data.length > 0) { + foundResult = true; + return data; + } + }, err => { + onUnexpectedError(err); + }); + } + } + })).then(values => { + return values[0] + }); +} class WordHighlighter { @@ -99,7 +126,7 @@ class WordHighlighter { } // no providers for this model - if(!DocumentHighlighterRegistry.has(this.model)) { + if(!OccurrencesRegistry.has(this.model)) { this._stopAll(); return; } @@ -175,25 +202,7 @@ class WordHighlighter { var myRequestId = ++this.workerRequestTokenId; this.workerRequestCompleted = false; - let foundResult = false; - let orderedByScore = DocumentHighlighterRegistry.ordered(this.model); - let resource = this.model.getAssociatedResource(); - let position = this.editor.getPosition(); - - // in order of score ask the occurrences provider - // until someone response with a good result - // (good = none empty array) - this.workerRequest = sequence(orderedByScore.map(provider => { - return () => { - if (!foundResult) { - return provider.findOccurrences(resource, position).then(data => { - if (Array.isArray(data) && data.length > 0) { - return data; - } - }); - } - } - })).then(values => values[0]); + this.workerRequest = getOccurrencesAtPosition(this.model, this.editor.getPosition()); this.workerRequest.then(data => { if (myRequestId === this.workerRequestTokenId) { diff --git a/src/vs/platform/thread/common/abstractThreadService.ts b/src/vs/platform/thread/common/abstractThreadService.ts index 8cbd86d9965..251646149c5 100644 --- a/src/vs/platform/thread/common/abstractThreadService.ts +++ b/src/vs/platform/thread/common/abstractThreadService.ts @@ -45,7 +45,8 @@ export abstract class AbstractThreadService implements remote.IManyHandler { public isInMainThread:boolean; - private _instantiationService:instantiation.IInstantiationService; + protected _instantiationService: instantiation.IInstantiationService; + _boundObjects:{[id:string]:IThreadSynchronizableObject;}; _pendingObjects:winjs.Promise[]; private _localObjMap: { [id:string]: any; }; diff --git a/src/vs/workbench/api/browser/pluginHost.api.impl.ts b/src/vs/workbench/api/browser/pluginHost.api.impl.ts index 3364186808d..eacd47d1fa8 100644 --- a/src/vs/workbench/api/browser/pluginHost.api.impl.ts +++ b/src/vs/workbench/api/browser/pluginHost.api.impl.ts @@ -17,11 +17,11 @@ import {PluginHostQuickOpen} from 'vs/workbench/api/browser/pluginHostQuickOpen' import {PluginHostStatusBar} from 'vs/workbench/api/browser/pluginHostStatusBar'; import {PluginHostCommands} from 'vs/workbench/api/common/pluginHostCommands'; import {ExtHostOutputService} from 'vs/workbench/api/browser/extHostOutputService'; -import {LanguageFeatures} from 'vs/workbench/api/common/languageFeatures'; import {PluginHostMessageService} from 'vs/workbench/api/common/pluginHostMessageService'; import {PluginHostTelemetryService} from 'vs/workbench/api/common/pluginHostTelemetry'; import {PluginHostEditors} from 'vs/workbench/api/common/pluginHostEditors'; import {ExtHostLanguages} from 'vs/workbench/api/common/extHostLanguages'; +import {ExtHostLanguageFeatures} from 'vs/workbench/api/common/extHostLanguageFeatures'; import * as extHostTypes from 'vs/workbench/api/common/pluginHostTypes'; import 'vs/workbench/api/common/pluginHostTypes.marshalling'; import {wrapAsWinJSPromise} from 'vs/base/common/async'; @@ -250,7 +250,7 @@ export class PluginHostAPIImplementation { // const languages = new ExtHostLanguages(this._threadService); const pluginHostDiagnostics = new PluginHostDiagnostics(this._threadService); - const features = LanguageFeatures.createExtensionHostInstances(this._threadService); + const languageFeatures = threadService.getRemotable(ExtHostLanguageFeatures); this.languages = { createDiagnosticCollection(name?: string): vscode.DiagnosticCollection { @@ -263,46 +263,46 @@ export class PluginHostAPIImplementation { return score(selector, { uri: document.uri, language: document.languageId }); }, registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider): vscode.Disposable { - return features.codeActions.register(selector, provider); + return languageFeatures.registerCodeActionProvider(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); + return languageFeatures.registerDefinitionProvider(selector, provider); }, registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable { - return features.hover.register(selector, provider); + return languageFeatures.registerHoverProvider(selector, provider); }, registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { - return features.documentHighlight.register(selector, provider); + return languageFeatures.registerDocumentHighlightProvider(selector, provider); }, registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable { - return features.referenceSearch.register(selector, provider); + return languageFeatures.registerReferenceProvider(selector, provider); }, registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { - return features.rename.register(selector, provider); + return languageFeatures.registerRenameProvider(selector, provider); }, registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable { - return features.documentSymbols.register(selector, provider); + return languageFeatures.registerDocumentSymbolProvider(selector, provider); }, registerWorkspaceSymbolProvider(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { - return features.workspaceSymbols.register(provider); + return languageFeatures.registerWorkspaceSymbolProvider(provider); }, registerDocumentFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable { - return features.formatDocument.register(selector, provider); + return languageFeatures.registerDocumentFormattingEditProvider(selector, provider); }, registerDocumentRangeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider): vscode.Disposable { - return features.formatRange.register(selector, provider); + return languageFeatures.registerDocumentRangeFormattingEditProvider(selector, provider); }, registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]): vscode.Disposable { - return features.formatOnType.register(selector, { triggerCharacters: [firstTriggerCharacter].concat(moreTriggerCharacters), provider }); + return languageFeatures.registerOnTypeFormattingEditProvider(selector, provider, [firstTriggerCharacter].concat(moreTriggerCharacters)); }, registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, ...triggerCharacters: string[]): vscode.Disposable { - return features.signatureHelp.register(selector, { triggerCharacters, provider }); + return languageFeatures.registerSignatureHelpProvider(selector, provider, triggerCharacters); }, registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, ...triggerCharacters: string[]): vscode.Disposable { - return features.completions.register(selector, { triggerCharacters, provider }); + return languageFeatures.registerCompletionItemProvider(selector, provider, triggerCharacters); }, setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration):vscode.Disposable => { return this._setLanguageConfiguration(language, configuration); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts new file mode 100644 index 00000000000..e638636c307 --- /dev/null +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -0,0 +1,1134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import URI from 'vs/base/common/uri'; +import Event, {Emitter} from 'vs/base/common/event'; +import Severity from 'vs/base/common/severity'; +import {DefaultFilter} from 'vs/editor/common/modes/modesFilters'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {onUnexpectedError} from 'vs/base/common/errors'; +import {sequence} from 'vs/base/common/async'; +import {Range as EditorRange} from 'vs/editor/common/core/range'; +import {IDisposable} from 'vs/base/common/lifecycle'; +import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; +import {Remotable, IThreadService} from 'vs/platform/thread/common/thread'; +import * as vscode from 'vscode'; +import * as TypeConverters from 'vs/workbench/api/common/pluginHostTypeConverters'; +import {Position, Range, SymbolKind, DocumentHighlightKind, Disposable, Diagnostic, DiagnosticSeverity, Location, SignatureHelp, CompletionItemKind} from 'vs/workbench/api/common/pluginHostTypes'; +import {IPosition, IRange, ISingleEditOperation} from 'vs/editor/common/editorCommon'; +import * as modes from 'vs/editor/common/modes'; +import {CancellationTokenSource} from 'vs/base/common/cancellation'; +import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments'; +import {IMarkerService, IMarker} from 'vs/platform/markers/common/markers'; +import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pluginHostCommands'; +import {DeclarationRegistry} from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; +import {ExtraInfoRegistry} from 'vs/editor/contrib/hover/common/hover'; +import {OccurrencesRegistry} from 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; +import {ReferenceRegistry} from 'vs/editor/contrib/referenceSearch/common/referenceSearch'; +import {QuickFixRegistry} from 'vs/editor/contrib/quickFix/common/quickFix'; +import {OutlineRegistry, IOutlineEntry, IOutlineSupport} from 'vs/editor/contrib/quickOpen/common/quickOpen'; +import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; +import {NavigateTypesSupportRegistry, INavigateTypesSupport, ITypeBearing} from 'vs/workbench/parts/search/common/search' +import {RenameRegistry} from 'vs/editor/contrib/rename/common/rename'; +import {FormatRegistry, FormatOnTypeRegistry} from 'vs/editor/contrib/format/common/format'; +import {CodeLensRegistry} from 'vs/editor/contrib/codelens/common/codelens'; +import {ParameterHintsRegistry} from 'vs/editor/contrib/parameterHints/common/parameterHints'; +import {SuggestRegistry} from 'vs/editor/contrib/suggest/common/suggest'; + +function isThenable(obj: any): obj is Thenable { + return obj && typeof obj['then'] === 'function'; +} + +function asWinJsPromise(callback: (token: vscode.CancellationToken) => T | Thenable): TPromise { + let source = new CancellationTokenSource(); + return new TPromise((resolve, reject) => { + let item = callback(source.token); + if (isThenable(item)) { + item.then(resolve, reject); + } else { + resolve(item); + } + }, () => { + source.cancel(); + }); +} + +// --- adapter + +class OutlineAdapter implements IOutlineSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.DocumentSymbolProvider; + + constructor(documents: PluginHostModelService, provider: vscode.DocumentSymbolProvider) { + this._documents = documents; + this._provider = provider; + } + + getOutline(resource: URI): TPromise { + let doc = this._documents.getDocument(resource); + return asWinJsPromise(token => this._provider.provideDocumentSymbols(doc, token)).then(value => { + if (Array.isArray(value)) { + return value.map(OutlineAdapter._convertSymbolInfo); + } + }); + } + + private static _convertSymbolInfo(symbol: vscode.SymbolInformation): IOutlineEntry { + return { + type: TypeConverters.fromSymbolKind(symbol.kind), + range: TypeConverters.fromRange(symbol.location.range), + containerLabel: symbol.containerName, + label: symbol.name, + icon: undefined, + }; + } +} + +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 { + 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 { + id: String(i), + range: TypeConverters.fromRange(lens.range) + } + }); + }); + } + + resolveCodeLensSymbol(resource: URI, symbol: modes.ICodeLensSymbol): TPromise { + + let lenses = this._cache[resource.toString()]; + if (!lenses) { + return; + } + + let lens = lenses[Number(symbol.id)]; + if (!lens) { + return; + } + + let resolve: TPromise; + 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: '<>', + command: 'missing', + } + } + return { + id: command.command, + title: command.title, + arguments: command.arguments + } + }); + } +} + +class DeclarationAdapter implements modes.IDeclarationSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.DefinitionProvider; + + constructor(documents: PluginHostModelService, provider: vscode.DefinitionProvider) { + this._documents = documents; + this._provider = provider; + } + + canFindDeclaration() { + return true; + } + + findDeclaration(resource: URI, position: IPosition): TPromise { + let doc = this._documents.getDocument(resource); + let pos = TypeConverters.toPosition(position); + return asWinJsPromise(token => this._provider.provideDefinition(doc, pos, token)).then(value => { + if (Array.isArray(value)) { + return value.map(DeclarationAdapter._convertLocation); + } else if (value) { + return DeclarationAdapter._convertLocation(value); + } + }); + } + + private static _convertLocation(location: vscode.Location): modes.IReference { + if (!location) { + return; + } + return { + resource: location.uri, + range: TypeConverters.fromRange(location.range) + }; + } +} + +class ExtraInfoAdapter implements modes.IExtraInfoSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.HoverProvider; + + constructor(documents: PluginHostModelService, provider: vscode.HoverProvider) { + this._documents = documents; + this._provider = provider; + } + + computeInfo(resource: URI, position: IPosition): TPromise { + + let doc = this._documents.getDocument(resource); + let pos = TypeConverters.toPosition(position); + + return asWinJsPromise(token => this._provider.provideHover(doc, pos, token)).then(value => { + if (!value) { + return; + } + + let {range, contents} = value; + + if (!range) { + range = doc.getWordRangeAtPosition(pos); + } + if (!range) { + range = new Range(pos, pos); + } + + return { + range: TypeConverters.fromRange(range), + htmlContent: contents && contents.map(TypeConverters.fromFormattedString) + } + }); + } +} + +class OccurrencesAdapter implements modes.IOccurrencesSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.DocumentHighlightProvider; + + constructor(documents: PluginHostModelService, provider: vscode.DocumentHighlightProvider) { + this._documents = documents; + this._provider = provider; + } + + findOccurrences(resource: URI, position: IPosition): TPromise { + + let doc = this._documents.getDocument(resource); + let pos = TypeConverters.toPosition(position); + + return asWinJsPromise(token => this._provider.provideDocumentHighlights(doc, pos, token)).then(value => { + if (Array.isArray(value)) { + return value.map(OccurrencesAdapter._convertDocumentHighlight); + } + }); + } + + private static _convertDocumentHighlight(documentHighlight: vscode.DocumentHighlight): modes.IOccurence { + return { + range: TypeConverters.fromRange(documentHighlight.range), + kind: DocumentHighlightKind[documentHighlight.kind].toString().toLowerCase() + } + } +} + +class ReferenceAdapter implements modes.IReferenceSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.ReferenceProvider; + + constructor(documents: PluginHostModelService, provider: vscode.ReferenceProvider) { + this._documents = documents; + this._provider = provider; + } + + canFindReferences():boolean { + return true + } + + findReferences(resource: URI, position: IPosition, includeDeclaration: boolean): TPromise { + let doc = this._documents.getDocument(resource); + let pos = TypeConverters.toPosition(position); + + return asWinJsPromise(token => this._provider.provideReferences(doc, pos, { includeDeclaration }, token)).then(value => { + if (Array.isArray(value)) { + return value.map(ReferenceAdapter._convertLocation); + } + }); + } + + private static _convertLocation(location: vscode.Location): modes.IReference { + return { + resource: location.uri, + range: TypeConverters.fromRange(location.range) + }; + } +} + +class QuickFixAdapter implements modes.IQuickFixSupport { + + private _documents: PluginHostModelService; + private _commands: PluginHostCommands; + private _provider: vscode.CodeActionProvider; + private _cache: { [key: string]: vscode.Command[] } = Object.create(null); + + constructor(documents: PluginHostModelService, commands: PluginHostCommands, provider: vscode.CodeActionProvider) { + this._documents = documents; + this._commands = commands; + this._provider = provider; + } + + getQuickFixes(resource: URI, range: IRange, marker?: IMarker[]): TPromise { + + // return this._executeCommand(resource, range, markers); + const key = resource.toString(); + delete this._cache[key]; + + const doc = this._documents.getDocument(resource); + const ran = TypeConverters.toRange(range); + const diagnostics = marker.map(marker => { + const diag = new Diagnostic(TypeConverters.toRange(marker), marker.message); + diag.code = marker.code; + diag.severity = TypeConverters.toDiagnosticSeverty(marker.severity); + return diag; + }); + + return asWinJsPromise(token => this._provider.provideCodeActions(doc, ran, { diagnostics: diagnostics }, token)).then(commands => { + if (!Array.isArray(commands)) { + return; + } + + this._cache[key] = commands; + + return commands.map((command, i) => { + return { + id: String(i), + label: command.title, + score: 1 + }; + }); + }); + } + + runQuickFixAction(resource: URI, range: IRange, id: string): any { + + let commands = this._cache[resource.toString()]; + if (!commands) { + return TPromise.wrapError('no command for ' + resource.toString()); + } + + let command = commands[Number(id)]; + if (!command) { + return TPromise.wrapError('no command for ' + resource.toString()); + } + + return this._commands.executeCommand(command.command, ...command.arguments); + } +} + +class DocumentFormattingAdapter implements modes.IFormattingSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.DocumentFormattingEditProvider; + + constructor(documents: PluginHostModelService, provider: vscode.DocumentFormattingEditProvider) { + this._documents = documents; + this._provider = provider; + } + + formatDocument(resource: URI, options: modes.IFormattingOptions): TPromise { + + let doc = this._documents.getDocument(resource); + + return asWinJsPromise(token => this._provider.provideDocumentFormattingEdits(doc, options, token)).then(value => { + if (Array.isArray(value)) { + return value.map(TypeConverters.fromTextEdit); + } + }); + } +} + +class RangeFormattingAdapter implements modes.IFormattingSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.DocumentRangeFormattingEditProvider; + + constructor(documents: PluginHostModelService, provider: vscode.DocumentRangeFormattingEditProvider) { + this._documents = documents; + this._provider = provider; + } + + formatRange(resource: URI, range: IRange, options: modes.IFormattingOptions): TPromise { + + let doc = this._documents.getDocument(resource); + let ran = TypeConverters.toRange(range); + + return asWinJsPromise(token => this._provider.provideDocumentRangeFormattingEdits(doc, ran, options, token)).then(value => { + if (Array.isArray(value)) { + return value.map(TypeConverters.fromTextEdit); + } + }); + } +} + +class OnTypeFormattingAdapter implements modes.IFormattingSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.OnTypeFormattingEditProvider; + + constructor(documents: PluginHostModelService, provider: vscode.OnTypeFormattingEditProvider) { + this._documents = documents; + this._provider = provider; + } + + autoFormatTriggerCharacters = []; // not here + + formatAfterKeystroke(resource: URI, position: IPosition, ch: string, options: modes.IFormattingOptions): TPromise { + + let doc = this._documents.getDocument(resource); + let pos = TypeConverters.toPosition(position); + + return asWinJsPromise(token => this._provider.provideOnTypeFormattingEdits(doc, pos, ch, options, token)).then(value => { + if (Array.isArray(value)) { + return value.map(TypeConverters.fromTextEdit); + } + }); + } +} + +class NavigateTypeAdapter implements INavigateTypesSupport { + + private _provider: vscode.WorkspaceSymbolProvider; + + constructor(provider: vscode.WorkspaceSymbolProvider) { + this._provider = provider; + } + + getNavigateToItems(search: string): TPromise { + return asWinJsPromise(token => this._provider.provideWorkspaceSymbols(search, token)).then(value => { + if (Array.isArray(value)) { + return value.map(NavigateTypeAdapter._fromSymbolInformation); + } + }); + } + + private static _fromSymbolInformation(info: vscode.SymbolInformation): ITypeBearing { + return { + name: info.name, + type: SymbolKind[info.kind || SymbolKind.Property].toLowerCase(), + range: TypeConverters.fromRange(info.location.range), + resourceUri: info.location.uri, + containerName: info.containerName, + parameters: '', + }; + } +} + +class RenameAdapter implements modes.IRenameSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.RenameProvider; + + constructor(documents: PluginHostModelService, provider: vscode.RenameProvider) { + this._documents = documents; + this._provider = provider; + } + + rename(resource: URI, position: IPosition, newName: string): TPromise { + + let doc = this._documents.getDocument(resource); + let pos = TypeConverters.toPosition(position); + + return asWinJsPromise(token => this._provider.provideRenameEdits(doc, pos, newName, token)).then(value => { + + if (!value) { + return; + } + + let result = { + currentName: undefined, + edits: [] + }; + + for (let entry of value.entries()) { + let [uri, textEdits] = entry; + for (let textEdit of textEdits) { + result.edits.push({ + resource: uri, + newText: textEdit.newText, + range: TypeConverters.fromRange(textEdit.range) + }); + } + } + return result; + }, err => { + if (typeof err === 'string') { + return { + currentName: undefined, + edits: undefined, + rejectReason: err + }; + } + return TPromise.wrapError(err); + }); + } +} + +class SuggestAdapter implements modes.ISuggestSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.CompletionItemProvider; + private _cache: { [key: string]: vscode.CompletionItem[] } = Object.create(null); + + constructor(documents: PluginHostModelService, provider: vscode.CompletionItemProvider) { + this._documents = documents; + this._provider = provider; + } + + suggest(resource: URI, position: IPosition): TPromise { + + const doc = this._documents.getDocument(resource); + const pos = TypeConverters.toPosition(position); + const ran = doc.getWordRangeAtPosition(pos); + + const key = resource.toString(); + delete this._cache[key]; + + return asWinJsPromise(token => this._provider.provideCompletionItems(doc, pos, token)).then(value => { + + let defaultSuggestions: modes.ISuggestions = { + suggestions: [], + currentWord: ran ? doc.getText(new Range(ran.start, pos)) : '', + }; + let allSuggestions: modes.ISuggestions[] = [defaultSuggestions]; + + + for (let i = 0; i < value.length; i++) { + const item = value[i]; + const suggestion = SuggestAdapter._convertCompletionItem(item); + + if (item.textEdit) { + + let editRange = item.textEdit.range; + + // invalid text edit + if (!editRange.isSingleLine || editRange.start.line !== pos.line) { + console.warn('INVALID text edit, must be single line and on the same line'); + continue; + } + + // insert the text of the edit and create a dedicated + // suggestion-container with overwrite[Before|After] + suggestion.codeSnippet = item.textEdit.newText; + + allSuggestions.push({ + currentWord: doc.getText(editRange), + suggestions: [suggestion], + overwriteBefore: pos.character - editRange.start.character, + overwriteAfter: editRange.end.character - pos.character + }); + + } else { + defaultSuggestions.suggestions.push(suggestion); + } + + // assign identifier to suggestion + suggestion.id = String(i); + } + + // cache for details call + this._cache[key] = value; + + return allSuggestions; + }); + } + + getSuggestionDetails(resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise { + if (typeof this._provider.resolveCompletionItem !== 'function') { + return TPromise.as(suggestion); + } + let items = this._cache[resource.toString()]; + if (!items) { + return TPromise.as(suggestion); + } + let item = items[Number(suggestion.id)]; + if (!item) { + return TPromise.as(suggestion); + } + return asWinJsPromise(token => this._provider.resolveCompletionItem(item, token)).then(resolvedItem => { + return SuggestAdapter._convertCompletionItem(resolvedItem || item); + }); + } + + private static _convertCompletionItem(item: vscode.CompletionItem): modes.ISuggestion { + return { + label: item.label, + codeSnippet: item.insertText || item.label, + type: CompletionItemKind[item.kind || CompletionItemKind.Text].toString().toLowerCase(), + typeLabel: item.detail, + documentationLabel: item.documentation, + sortText: item.sortText, + filterText: item.filterText + }; + } + + getFilter(): any{ + throw new Error('illegal state'); + } + getTriggerCharacters(): string[] { + throw new Error('illegal state'); + } + shouldShowEmptySuggestionList(): boolean { + throw new Error('illegal state'); + } + shouldAutotriggerSuggest(context: modes.ILineContext, offset: number, triggeredByCharacter: string): boolean { + throw new Error('illegal state'); + } +} + +class ParameterHintsAdapter implements modes.IParameterHintsSupport { + + private _documents: PluginHostModelService; + private _provider: vscode.SignatureHelpProvider; + + constructor(documents: PluginHostModelService, provider: vscode.SignatureHelpProvider) { + this._documents = documents; + this._provider = provider; + } + + getParameterHints(resource: URI, position: IPosition, triggerCharacter?: string): TPromise { + + const doc = this._documents.getDocument(resource); + const pos = TypeConverters.toPosition(position); + + return asWinJsPromise(token => this._provider.provideSignatureHelp(doc, pos, token)).then(value => { + if (value instanceof SignatureHelp) { + return ParameterHintsAdapter._convertSignatureHelp(value); + } + }); + } + + private static _convertSignatureHelp(signatureHelp: SignatureHelp): modes.IParameterHints { + + let result: modes.IParameterHints = { + currentSignature: signatureHelp.activeSignature, + currentParameter: signatureHelp.activeParameter, + signatures: [] + } + + for (let signature of signatureHelp.signatures) { + + let signatureItem: modes.ISignature = { + label: signature.label, + documentation: signature.documentation, + parameters: [] + }; + + let idx = 0; + for (let parameter of signature.parameters) { + + let parameterItem: modes.IParameter = { + label: parameter.label, + documentation: parameter.documentation, + }; + + signatureItem.parameters.push(parameterItem); + idx = signature.label.indexOf(parameter.label, idx); + + if (idx >= 0) { + parameterItem.signatureLabelOffset = idx; + idx += parameter.label.length; + parameterItem.signatureLabelEnd = idx; + } + } + + result.signatures.push(signatureItem); + } + + return result; + } + + getParameterHintsTriggerCharacters(): string[] { + throw new Error('illegal state'); + } + + shouldTriggerParameterHints(context: modes.ILineContext, offset: number): boolean { + throw new Error('illegal state'); + } +} + +type Adapter = OutlineAdapter | CodeLensAdapter | DeclarationAdapter | ExtraInfoAdapter + | OccurrencesAdapter | ReferenceAdapter | QuickFixAdapter | DocumentFormattingAdapter + | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter + | SuggestAdapter | ParameterHintsAdapter; + +@Remotable.PluginHostContext('ExtHostLanguageFeatures') +export class ExtHostLanguageFeatures { + + private static _handlePool = 0; + + private _proxy: MainThreadLanguageFeatures; + private _documents: PluginHostModelService; + private _commands: PluginHostCommands; + private _adapter: { [handle: number]: Adapter } = Object.create(null); + + constructor( @IThreadService threadService: IThreadService) { + this._proxy = threadService.getRemotable(MainThreadLanguageFeatures); + this._documents = threadService.getRemotable(PluginHostModelService); + this._commands = threadService.getRemotable(PluginHostCommands); + } + + private _createDisposable(handle: number): Disposable { + return new Disposable(() => { + delete this._adapter[handle]; + this._proxy.$unregister(handle); + }); + } + + private _nextHandle(): number { + return ExtHostLanguageFeatures._handlePool++; + } + + private _withAdapter(handle: number, ctor: { new (...args: any[]): A }, callback: (adapter: A) => TPromise): TPromise { + let adapter = this._adapter[handle]; + if (!(adapter instanceof ctor)) { + return TPromise.wrapError(new Error('no adapter found')); + } + return callback( adapter); + } + + // --- outline + + registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable { + 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{ + return this._withAdapter(handle, OutlineAdapter, adapter => adapter.getOutline(resource)); + } + + // --- 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 { + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.findCodeLensSymbols(resource)); + } + + $resolveCodeLensSymbol(handle:number, resource: URI, symbol: modes.ICodeLensSymbol): TPromise { + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLensSymbol(resource, symbol)); + } + + // --- declaration + + registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new DeclarationAdapter(this._documents, provider); + this._proxy.$registerDeclaractionSupport(handle, selector); + return this._createDisposable(handle); + } + + $findDeclaration(handle: number, resource: URI, position: IPosition): TPromise { + return this._withAdapter(handle, DeclarationAdapter, adapter => adapter.findDeclaration(resource, position)); + } + + // --- extra info + + registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new ExtraInfoAdapter(this._documents, provider); + this._proxy.$registerExtraInfoSupport(handle, selector); + return this._createDisposable(handle); + } + + $computeInfo(handle:number, resource: URI, position: IPosition): TPromise { + return this._withAdapter(handle, ExtraInfoAdapter, adpater => adpater.computeInfo(resource, position)); + } + + // --- occurrences + + registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new OccurrencesAdapter(this._documents, provider); + this._proxy.$registerOccurrencesSupport(handle, selector); + return this._createDisposable(handle); + } + + $findOccurrences(handle:number, resource: URI, position: IPosition): TPromise { + return this._withAdapter(handle, OccurrencesAdapter, adapter => adapter.findOccurrences(resource, position)); + } + + // --- references + + registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new ReferenceAdapter(this._documents, provider); + this._proxy.$registerReferenceSupport(handle, selector); + return this._createDisposable(handle); + } + + $findReferences(handle: number, resource: URI, position: IPosition, includeDeclaration: boolean): TPromise { + return this._withAdapter(handle, ReferenceAdapter, adapter => adapter.findReferences(resource, position, includeDeclaration)); + } + + // --- quick fix + + registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new QuickFixAdapter(this._documents, this._commands, provider); + this._proxy.$registerQuickFixSupport(handle, selector); + return this._createDisposable(handle); + } + + $getQuickFixes(handle:number, resource: URI, range: IRange, marker: IMarker[]): TPromise { + return this._withAdapter(handle, QuickFixAdapter, adapter => adapter.getQuickFixes(resource, range, marker)); + } + + $runQuickFixAction(handle: number, resource: URI, range: IRange, id: string): any { + return this._withAdapter(handle, QuickFixAdapter, adapter => adapter.runQuickFixAction(resource, range, id)); + } + + // --- formatting + + registerDocumentFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new DocumentFormattingAdapter(this._documents, provider); + this._proxy.$registerDocumentFormattingSupport(handle, selector); + return this._createDisposable(handle); + } + + $formatDocument(handle: number, resource: URI, options: modes.IFormattingOptions): TPromise{ + return this._withAdapter(handle, DocumentFormattingAdapter, adapter => adapter.formatDocument(resource, options)); + } + + registerDocumentRangeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new RangeFormattingAdapter(this._documents, provider); + this._proxy.$registerRangeFormattingSupport(handle, selector); + return this._createDisposable(handle); + } + + $formatRange(handle: number, resource: URI, range: IRange, options: modes.IFormattingOptions): TPromise{ + return this._withAdapter(handle, RangeFormattingAdapter, adapter => adapter.formatRange(resource, range, options)); + } + + registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, triggerCharacters: string[]): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new OnTypeFormattingAdapter(this._documents, provider); + this._proxy.$registerOnTypeFormattingSupport(handle, selector, triggerCharacters); + return this._createDisposable(handle); + } + + $formatAfterKeystroke(handle: number, resource: URI, position: IPosition, ch: string, options: modes.IFormattingOptions): TPromise{ + return this._withAdapter(handle, OnTypeFormattingAdapter, adapter => adapter.formatAfterKeystroke(resource, position, ch, options)); + } + + // --- navigate types + + registerWorkspaceSymbolProvider(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new NavigateTypeAdapter(provider); + this._proxy.$registerNavigateTypeSupport(handle); + return this._createDisposable(handle); + } + + $getNavigateToItems(handle: number, search: string): TPromise { + return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.getNavigateToItems(search)); + } + + // --- rename + + registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new RenameAdapter(this._documents, provider); + this._proxy.$registerRenameSupport(handle, selector); + return this._createDisposable(handle); + } + + $rename(handle: number, resource: URI, position: IPosition, newName: string): TPromise { + return this._withAdapter(handle, RenameAdapter, adapter => adapter.rename(resource, position, newName)); + } + + // --- suggestion + + registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new SuggestAdapter(this._documents, provider); + this._proxy.$registerSuggestSupport(handle, selector, triggerCharacters); + return this._createDisposable(handle); + } + + $suggest(handle: number, resource: URI, position: IPosition): TPromise { + return this._withAdapter(handle, SuggestAdapter, adapter => adapter.suggest(resource, position)); + } + + $getSuggestionDetails(handle: number, resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise { + return this._withAdapter(handle, SuggestAdapter, adapter => adapter.getSuggestionDetails(resource, position, suggestion)); + } + + // --- parameter hints + + registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, triggerCharacters: string[]): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter[handle] = new ParameterHintsAdapter(this._documents, provider); + this._proxy.$registerParameterHintsSupport(handle, selector, triggerCharacters); + return this._createDisposable(handle); + } + + $getParameterHints(handle: number, resource: URI, position: IPosition, triggerCharacter?: string): TPromise { + return this._withAdapter(handle, ParameterHintsAdapter, adapter => adapter.getParameterHints(resource, position, triggerCharacter)); + } +} + +@Remotable.MainContext('MainThreadLanguageFeatures') +export class MainThreadLanguageFeatures { + + private _proxy: ExtHostLanguageFeatures; + private _markerService: IMarkerService; + private _registrations: { [handle: number]: IDisposable; } = Object.create(null); + + constructor( @IThreadService threadService: IThreadService, @IMarkerService markerService: IMarkerService) { + this._proxy = threadService.getRemotable(ExtHostLanguageFeatures); + this._markerService = markerService; + } + + $unregister(handle: number): TPromise { + let registration = this._registrations[handle]; + if (registration) { + registration.dispose(); + delete this._registrations[handle]; + } + return undefined; + } + + // --- outline + + $registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = OutlineRegistry.register(selector, { + getOutline: (resource: URI): TPromise => { + return this._proxy.$getOutline(handle, resource); + } + }); + return undefined; + } + + // --- code lens + + $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = CodeLensRegistry.register(selector, { + findCodeLensSymbols: (resource: URI): TPromise => { + return this._proxy.$findCodeLensSymbols(handle, resource); + }, + resolveCodeLensSymbol: (resource: URI, symbol: modes.ICodeLensSymbol): TPromise => { + return this._proxy.$resolveCodeLensSymbol(handle, resource, symbol); + } + }); + return undefined; + } + + // --- declaration + + $registerDeclaractionSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = DeclarationRegistry.register(selector, { + canFindDeclaration() { + return true; + }, + findDeclaration: (resource: URI, position: IPosition): TPromise => { + return this._proxy.$findDeclaration(handle, resource, position); + } + }); + return undefined; + } + + // --- extra info + + $registerExtraInfoSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = ExtraInfoRegistry.register(selector, { + computeInfo: (resource: URI, position: IPosition): TPromise => { + return this._proxy.$computeInfo(handle, resource, position); + } + }); + return undefined; + } + + // --- occurrences + + $registerOccurrencesSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = OccurrencesRegistry.register(selector, { + findOccurrences: (resource: URI, position: IPosition): TPromise => { + return this._proxy.$findOccurrences(handle, resource, position); + } + }); + return undefined; + } + + // --- references + + $registerReferenceSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = ReferenceRegistry.register(selector, { + canFindReferences() { + return true; + }, + findReferences: (resource: URI, position: IPosition, includeDeclaration: boolean): TPromise => { + return this._proxy.$findReferences(handle, resource, position, includeDeclaration); + } + }); + return undefined; + } + + // --- quick fix + + $registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = QuickFixRegistry.register(selector, { + getQuickFixes: (resource: URI, range: IRange): TPromise => { + let markers: IMarker[] = []; + this._markerService.read({ resource }).forEach(marker => { + if (EditorRange.lift(marker).intersectRanges(range)) { + markers.push(marker); + } + }); + return this._proxy.$getQuickFixes(handle, resource, range, markers); + }, + runQuickFixAction: (resource: URI, range: IRange, id: string) => { + return this._proxy.$runQuickFixAction(handle, resource, range, id); + } + }); + return undefined; + } + + // --- formatting + + $registerDocumentFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = FormatRegistry.register(selector, { + formatDocument: (resource: URI, options: modes.IFormattingOptions): TPromise => { + return this._proxy.$formatDocument(handle, resource, options); + } + }); + return undefined; + } + + $registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = FormatRegistry.register(selector, { + formatRange: (resource: URI, range: IRange, options: modes.IFormattingOptions): TPromise => { + return this._proxy.$formatRange(handle, resource, range, options); + } + }); + return undefined; + } + + $registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): TPromise { + this._registrations[handle] = FormatOnTypeRegistry.register(selector, { + + autoFormatTriggerCharacters, + + formatAfterKeystroke: (resource: URI, position: IPosition, ch: string, options: modes.IFormattingOptions): TPromise => { + return this._proxy.$formatAfterKeystroke(handle, resource, position, ch, options); + } + }); + return undefined; + } + + // --- navigate type + + $registerNavigateTypeSupport(handle: number): TPromise { + this._registrations[handle] = NavigateTypesSupportRegistry.register({ + getNavigateToItems: (search: string): TPromise => { + return this._proxy.$getNavigateToItems(handle, search); + } + }); + return undefined; + } + + // --- rename + + $registerRenameSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + this._registrations[handle] = RenameRegistry.register(selector, { + rename: (resource: URI, position: IPosition, newName: string): TPromise => { + return this._proxy.$rename(handle, resource, position, newName); + } + }); + return undefined; + } + + // --- suggest + + $registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[]): TPromise { + this._registrations[handle] = SuggestRegistry.register(selector, { + suggest: (resource: URI, position: IPosition, triggerCharacter?: string): TPromise => { + return this._proxy.$suggest(handle, resource, position); + }, + getSuggestionDetails: (resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise => { + return this._proxy.$getSuggestionDetails(handle, resource, position, suggestion); + }, + getFilter() { + return DefaultFilter; + }, + getTriggerCharacters(): string[] { + return triggerCharacters; + }, + shouldShowEmptySuggestionList(): boolean { + return true; + }, + shouldAutotriggerSuggest(): boolean { + return true; + } + }); + return undefined; + } + + // --- parameter hints + + $registerParameterHintsSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise { + this._registrations[handle] = ParameterHintsRegistry.register(selector, { + getParameterHints: (resource: URI, position: IPosition, triggerCharacter?: string): TPromise => { + return this._proxy.$getParameterHints(handle, resource, position, triggerCharacter); + }, + getParameterHintsTriggerCharacters(): string[] { + return triggerCharacter; + }, + shouldTriggerParameterHints(context: modes.ILineContext, offset: number): boolean { + return true; + } + }); + return undefined; + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/common/languageFeatures.ts b/src/vs/workbench/api/common/languageFeatures.ts deleted file mode 100644 index b5caf5ddbd2..00000000000 --- a/src/vs/workbench/api/common/languageFeatures.ts +++ /dev/null @@ -1,1293 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import URI from 'vs/base/common/uri'; -import Event, {Emitter} from 'vs/base/common/event'; -import Severity from 'vs/base/common/severity'; -import {TPromise} from 'vs/base/common/winjs.base'; -import {sequence} from 'vs/base/common/async'; -import {Range as EditorRange} from 'vs/editor/common/core/range'; -import {IDisposable} from 'vs/base/common/lifecycle'; -import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; -import {Remotable, IThreadService} from 'vs/platform/thread/common/thread'; -import * as vscode from 'vscode'; -import * as TypeConverters from 'vs/workbench/api/common/pluginHostTypeConverters'; -import {Position, Range, SymbolKind, DocumentHighlightKind, Disposable, Diagnostic, DiagnosticSeverity, Location, SignatureHelp, CompletionItemKind} from 'vs/workbench/api/common/pluginHostTypes'; -import {IPosition, IRange, ISingleEditOperation} from 'vs/editor/common/editorCommon'; -import * as modes from 'vs/editor/common/modes'; -import {CancellationTokenSource} from 'vs/base/common/cancellation'; -import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments'; -import {IMarkerService, IMarker} from 'vs/platform/markers/common/markers'; -import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pluginHostCommands'; -import DeclarationRegistry from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; -import ExtraInfoRegistry from 'vs/editor/contrib/hover/common/hover'; -import DocumentHighlighterRegistry from 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; -import ReferenceSearchRegistry from 'vs/editor/contrib/referenceSearch/common/referenceSearch'; -import QuickFixRegistry from 'vs/editor/contrib/quickFix/common/quickFix'; -import QuickOutlineRegistry, {IOutlineEntry, IOutlineSupport} from 'vs/editor/contrib/quickOpen/common/quickOpen'; -import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; -import {NavigateTypesSupportRegistry, INavigateTypesSupport, ITypeBearing} from 'vs/workbench/parts/search/common/search' -import {RenameRegistry} from 'vs/editor/contrib/rename/common/rename'; -import {FormatRegistry, FormatOnTypeRegistry} from 'vs/editor/contrib/format/common/format'; -import {CodeLensRegistry} from 'vs/editor/contrib/codelens/common/codelens'; -import {ParameterHintsRegistry} from 'vs/editor/contrib/parameterHints/common/parameterHints'; -import {SuggestRegistry} from 'vs/editor/contrib/suggest/common/suggest'; - -function isThenable(obj: any): obj is Thenable { - return obj && typeof obj['then'] === 'function'; -} - -function asWinJsPromise(callback: (token: vscode.CancellationToken) => T | Thenable): TPromise { - let source = new CancellationTokenSource(); - return new TPromise((resolve, reject) => { - let item = callback(source.token); - if (isThenable(item)) { - item.then(resolve, reject); - } else { - resolve(item); - } - }, () => { - source.cancel(); - }); -} - -export abstract class AbstractMainThreadFeature { - - private _id: string; - protected _commands: PluginHostCommands; - protected _refCount = 0; - protected _disposable: IDisposable; - protected _registry: LanguageFeatureRegistry; - - constructor(id: string, registry: LanguageFeatureRegistry, @IThreadService threadService: IThreadService) { - this._id = id; - this._registry = registry; - this._commands = threadService.getRemotable(PluginHostCommands); - } - - _getId(): TPromise { - return TPromise.as(this._id); - } - - _register(selector: vscode.DocumentSelector): TPromise { - if (this._refCount++ === 0) { - this._disposable = this._registry.register(selector, this); - } - return undefined; - } - - _unregister(): TPromise { - if (--this._refCount === 0) { - this._disposable.dispose(); - } - return undefined; - } - - _executeCommand(...args:any[]):TPromise { - let result = this._commands.executeCommand(this._id, ...args); - return new TPromise((c, e) => { - result.then(c, e); - }); - } -} - -export abstract class AbstractExtensionHostFeature> { - - protected _commands: PluginHostCommands; - protected _proxy: P; - protected _registry = new LanguageFeatureRegistry(); - protected _models: PluginHostModelService; - - constructor(proxy: P, @IThreadService threadService: IThreadService) { - this._proxy = proxy; - this._models = threadService.getRemotable(PluginHostModelService); - this._commands = threadService.getRemotable(PluginHostCommands); - - proxy._getId().then(value => this._commands.registerCommand(value, this._runAsCommand, this)); - } - - register(selector: vscode.DocumentSelector, provider: T): vscode.Disposable { - - let disposable = this._registry.register(selector, provider); - let registered = this._proxy._register(selector); - - return new Disposable(() => { - disposable.dispose(); // remove locally - registered.then(() => this._proxy._unregister()); - }); - } - - protected abstract _runAsCommand(...args: any[]): any; - - protected _getAllFor(document: vscode.TextDocument): T[] { - return this._registry.all({ - language: document.languageId, - uri: document.uri - }); - } - - protected _getOrderedFor(document: vscode.TextDocument): T[] { - return this._registry.ordered({ - language: document.languageId, - uri: document.uri - }); - } -} - -// ---- definition feature - -export class ExtensionHostDefinitionFeature extends AbstractExtensionHostFeature { - - constructor(@IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadDefinitionFeature), threadService); - } - - _runAsCommand(resource: URI, position: IPosition): TPromise { - - let document = this._models.getDocument(resource); - let pos = TypeConverters.toPosition(position); - let locations: vscode.Location[] = []; - - let promises = this._registry.all({ language: document.languageId, uri: document.uri }).map(provider => { - return asWinJsPromise(token => provider.provideDefinition(document, pos, token)).then(result => { - if (Array.isArray(result)) { - locations.push(...result); - } else { - locations.push( result); - } - }, err => { - console.error(err); - }); - }); - - return TPromise.join(promises).then(() => { - return locations.map(ExtensionHostDefinitionFeature._convertLocation); - }) - } - - private static _convertLocation(location: vscode.Location): modes.IReference { - if (!location) { - return; - } - return { - resource: location.uri, - range: TypeConverters.fromRange(location.range) - }; - } -} - -@Remotable.MainContext('MainThreadDefinitionProvider') -export class MainThreadDefinitionFeature extends AbstractMainThreadFeature implements modes.IDeclarationSupport { - - constructor(@IThreadService threadService: IThreadService) { - super('vscode.executeDefinitionProvider', DeclarationRegistry, threadService); - } - - canFindDeclaration() { - return true - } - - findDeclaration(resource: URI, position: IPosition): TPromise{ - return this._executeCommand(resource, position); - } -} - -// ---- hover - - -export class ExtensionHostHoverFeature extends AbstractExtensionHostFeature { - - constructor( @IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadHoverFeature), threadService); - } - - _runAsCommand(resource: URI, position: IPosition): TPromise { - - let document = this._models.getDocument(resource); - let pos = TypeConverters.toPosition(position); - - // incrementally building up the result - let contents: vscode.MarkedString[] = []; - let word = document.getWordRangeAtPosition(pos); - let start = word && word.start || pos; - let end = word && word.end || pos; - - let promises = this._registry.all({ language: document.languageId, uri: document.uri }).map(provider => { - - return asWinJsPromise(token => provider.provideHover(document, pos, token)).then(result => { - - if (!result) { - return; - } - - if (result.range) { - if (result.range.start.isBefore(start)) { - start = result.range.start; - } - if (end.isBefore(result.range.end)) { - end = result.range.end; - } - } - - for (let markedString of result.contents) { - if (markedString) { - contents.push(markedString); - } - } - contents.push('\n'); - - }, err => { - console.error(err); - }); - }); - - return TPromise.join(promises).then(() => { - - contents.pop(); // remove the last '\n' element we added - - return { - range: TypeConverters.fromRange(new Range(start, end)), - htmlContent: contents.map(TypeConverters.fromFormattedString) - }; - }); - } -} - -@Remotable.MainContext('MainThreadHoverFeature') -export class MainThreadHoverFeature extends AbstractMainThreadFeature implements modes.IExtraInfoSupport { - - constructor(@IThreadService threadService: IThreadService) { - super('vscode.executeHoverProvider', ExtraInfoRegistry, threadService); - } - - computeInfo(resource: URI, position: IPosition): TPromise { - return this._executeCommand(resource, position); - } -} - -// --- occurrences - - -export class ExtensionHostOccurrencesFeature extends AbstractExtensionHostFeature { - - constructor(@IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadOccurrencesFeature), threadService); - } - - _runAsCommand(resource: URI, position: IPosition): TPromise { - - let document = this._models.getDocument(resource); - let pos = TypeConverters.toPosition(position); - let highlights: vscode.DocumentHighlight[]; - - let factory = this._getOrderedFor(document).map(provider => { - return () => { - if (!highlights) { - return asWinJsPromise(token => provider.provideDocumentHighlights(document, pos, token)).then(result => { - if (Array.isArray(result) && result.length > 0) { - highlights = result; - } - }, err => { - console.error(err); - }); - } - } - }); - - return sequence(factory).then(() => { - if (highlights) { - return highlights.map(ExtensionHostOccurrencesFeature._convertDocumentHighlight); - } - }); - } - - private static _convertDocumentHighlight(documentHighlight: vscode.DocumentHighlight): modes.IOccurence { - return { - range: TypeConverters.fromRange(documentHighlight.range), - kind: DocumentHighlightKind[documentHighlight.kind].toString().toLowerCase() - } - } -} - -@Remotable.MainContext('MainThreadOccurrencesFeature') -export class MainThreadOccurrencesFeature extends AbstractMainThreadFeature { - - constructor(@IThreadService threadService: IThreadService) { - super('vscode.executeDocumentHighlights', DocumentHighlighterRegistry, threadService); - } - - findOccurrences(resource: URI, position: IPosition): TPromise { - return this._executeCommand(resource, position); - } -} - -// --- reference search - -export class ExtensionHostReferenceSearch extends AbstractExtensionHostFeature { - - constructor(@IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadReferenceSearch), threadService); - } - - protected _runAsCommand(resource: URI, position: IPosition, includeDeclaration: boolean): TPromise { - - if (!(resource instanceof URI)) { - return TPromise.wrapError('resource expected'); - } - - let document = this._models.getDocument(resource); - let pos = TypeConverters.toPosition(position); - let locations: vscode.Location[] = []; - - let promises = this._registry.all({ language: document.languageId, uri: document.uri }).map(provider => { - return asWinJsPromise(token => provider.provideReferences(document, pos, { includeDeclaration }, token)).then(result => { - if (Array.isArray(result)) { - locations.push(...result); - } - }, err => { - console.error(err); - }); - }); - - return TPromise.join(promises).then(() => { - return locations.map(ExtensionHostReferenceSearch._convertLocation); - }); - } - - private static _convertLocation(location: vscode.Location): modes.IReference { - return { - resource: location.uri, - range: TypeConverters.fromRange(location.range) - }; - } -} - -@Remotable.MainContext('MainThreadReferenceSearch') -export class MainThreadReferenceSearch extends AbstractMainThreadFeature implements modes.IReferenceSupport { - - constructor(@IThreadService threadService: IThreadService) { - super('vscode.executeReferenceProvider', ReferenceSearchRegistry, threadService); - } - - canFindReferences():boolean { - return true - } - - findReferences(resource: URI, position: IPosition, includeDeclaration: boolean): TPromise { - return this._executeCommand(resource, position, includeDeclaration); - } -} - -// --- quick fix aka code actions - -export class ExtensionHostCodeActions extends AbstractExtensionHostFeature { - - private _disposable: Disposable; - - constructor(@IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadCodeActions), threadService); - } - - _runAsCommand(resource: URI, range: IRange, marker:IMarker[]): TPromise { - - let document = this._models.getDocument(resource); - let _range = TypeConverters.toRange(range); - let commands: vscode.Command[] = []; - - let diagnostics = marker.map(marker => { - let diag = new Diagnostic(TypeConverters.toRange(marker), marker.message); - diag.code = marker.code; - diag.severity = TypeConverters.toDiagnosticSeverty(marker.severity); - return diag; - }); - - let promises = this._getAllFor(document).map(provider => { - return asWinJsPromise(token => provider.provideCodeActions(document, _range, { diagnostics }, token)).then(result => { - if (Array.isArray(result)) { - commands.push(...result); - } - }, err => { - console.error(err); - }); - }); - - return TPromise.join(promises).then(() => { - if (this._disposable) { - this._disposable.dispose(); - } - - let disposables: IDisposable[] = []; - let quickFixes: modes.IQuickFix[] = []; - - commands.forEach((command, i) => { - - let id = '_code_action_action_wrapper_#' + i; - - // create fake action such that the aruments don't - // have to be send between ext-host - disposables.push(this._commands.registerCommand(id, () => { - return this._commands.executeCommand(command.command, ...command.arguments); - })); - - // create quick fix - quickFixes.push({ - id, - label: command.title, - score: 1 - }); - }); - - // not very nice... we need - // some sort of event to tell us when - // quick fix is bored of our commands - this._disposable = Disposable.from(...disposables); - return quickFixes; - }); - } -} - -@Remotable.MainContext('MainThreadCodeAction') -export class MainThreadCodeActions extends AbstractMainThreadFeature implements modes.IQuickFixSupport { - - private _keybindingService: IKeybindingService; - private _markerService: IMarkerService; - - constructor( @IThreadService threadService: IThreadService, @IKeybindingService keybindingService: IKeybindingService, - @IMarkerService markerService: IMarkerService) { - - super('vscode.executeCodeActionProvider', QuickFixRegistry, threadService); - this._keybindingService = keybindingService; - this._markerService = markerService; - } - - getQuickFixes(resource: URI, range: IRange): TPromise { - - let markers: IMarker[] = []; - this._markerService.read({ resource }).forEach(marker => { - if (EditorRange.lift(marker).intersectRanges(range)) { - markers.push(marker); - } - }); - - return this._executeCommand(resource, range, markers); - } - - runQuickFixAction (resource:URI, range:IRange, id:string) { - return TPromise.as(this._keybindingService.executeCommand(id)); - } -} - -// ---- OutlineSupport aka DocumentSymbols - -export class ExtensionHostDocumentSymbols extends AbstractExtensionHostFeature { - - constructor( @IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadDocumentSymbols), threadService); - } - - protected _runAsCommand(resource: URI): TPromise { - - 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 { - 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 implements IOutlineSupport { - - constructor( @IThreadService threadService: IThreadService) { - super('vscode.executeDocumentSymbolProvider', QuickOutlineRegistry, threadService); - } - - getOutline(resource: URI): TPromise{ - return this._executeCommand(resource); - } -} - -// -- Rename provider - -export class ExtensionHostRename extends AbstractExtensionHostFeature { - - constructor( @IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadRename), threadService); - } - - _runAsCommand(resource: URI, position: IPosition, newName: string): TPromise { - - let document = this._models.getDocument(resource); - let pos = TypeConverters.toPosition(position); - - let hasResult = false; - let rejects: string[] = []; - let factory = this._getOrderedFor(document).map(provider => { - return () => { - if (!hasResult) { - return asWinJsPromise(token => provider.provideRenameEdits(document, pos, newName, token)).then(result => { - if (result && result.size > 0) { - hasResult = true; - return result; - } - }, err => { - if (typeof err === 'string') { - rejects.push(err); - } - }); - } - }; - }); - - return sequence(factory).then(results => { - let rename = results[0]; - if (!rename) { - return { - rejectReason: rejects.join('\n'), - edits: undefined, - currentName: undefined - }; - } - - let result = { - currentName: undefined, - edits: [] - }; - for (let entry of rename.entries()) { - let [uri, textEdits] = entry; - for (let textEdit of textEdits) { - result.edits.push({ - resource: uri, - newText: textEdit.newText, - range: TypeConverters.fromRange(textEdit.range) - }); - } - } - return result; - }); - } -} - -@Remotable.MainContext('MainThreadRename') -export class MainThreadRename extends AbstractMainThreadFeature implements modes.IRenameSupport { - - constructor( @IThreadService threadService: IThreadService) { - super('vscode.executeDocumentRenameProvider', RenameRegistry, threadService); - } - - rename(resource: URI, position: IPosition, newName: string): TPromise { - return this._executeCommand(resource, position, newName); - } -} - -// --- format - -export class ExtHostFormatDocument extends AbstractExtensionHostFeature { - - constructor( @IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadFormatDocument), threadService); - } - - _runAsCommand(resource: URI, options: modes.IFormattingOptions): TPromise { - - let document = this._models.getDocument(resource); - let provider = this._getOrderedFor(document)[0]; - - return asWinJsPromise(token => provider.provideDocumentFormattingEdits(document, options, token)).then(result => { - if (Array.isArray(result)) { - return result.map(ExtHostFormatDocument.convertTextEdit); - } - }); - } - - static convertTextEdit(edit: vscode.TextEdit): ISingleEditOperation { - return { - text: edit.newText, - range: TypeConverters.fromRange(edit.range) - } - } -} - -@Remotable.MainContext('MainThreadFormatDocument') -export class MainThreadFormatDocument extends AbstractMainThreadFeature implements modes.IFormattingSupport { - - constructor( @IThreadService threadService: IThreadService) { - super('vscode.executeFormatDocumentProvider', FormatRegistry, threadService); - } - - formatDocument(resource: URI, options: modes.IFormattingOptions):TPromise { - return this._executeCommand(resource, options); - } -} - -export class ExtHostFormatRange extends AbstractExtensionHostFeature { - - constructor( @IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadFormatRange), threadService); - } - - _runAsCommand(resource: URI, range: IRange, options: modes.IFormattingOptions): TPromise { - - let document = this._models.getDocument(resource); - let provider = this._getOrderedFor(document)[0]; - let ran: Range; - - if (range) { - ran = TypeConverters.toRange(range); - } else { - let lastLine = document.lineAt(document.lineCount - 1); - let {line, character} = lastLine.range.end; - ran = new Range(0, 0, line, character); - } - - return asWinJsPromise(token => provider.provideDocumentRangeFormattingEdits(document, ran, options, token)).then(result => { - if (Array.isArray(result)) { - return result.map(ExtHostFormatDocument.convertTextEdit); - } - }); - } -} - -@Remotable.MainContext('MainThreadFormatRange') -export class MainThreadFormatRange extends AbstractMainThreadFeature implements modes.IFormattingSupport { - - constructor( @IThreadService threadService: IThreadService) { - super('vscode.executeFormatRangeProvider', FormatRegistry, threadService); - } - - formatRange(resource: URI, range:IRange, options: modes.IFormattingOptions):TPromise { - return this._executeCommand(resource, range, options); - } -} - -// --- format on type - -export interface FormatOnTypeEntry { - triggerCharacters: string[]; - provider: vscode.OnTypeFormattingEditProvider; -} - -export class ExtHostFormatOnType extends AbstractExtensionHostFeature { - - constructor( @IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadFormatOnType), threadService); - } - - register(selector: vscode.DocumentSelector, provider: FormatOnTypeEntry): vscode.Disposable { - - let disposable = this._registry.register(selector, provider); - let registered = this._proxy._register(selector, provider.triggerCharacters); - - return new Disposable(() => { - disposable.dispose(); - registered.then(() => this._proxy._unregister()); - }); - } - - _runAsCommand(resource: URI, position: IPosition, ch: string, options: modes.IFormattingOptions): TPromise { - - let document = this._models.getDocument(resource); - let pos = TypeConverters.toPosition(position); - - let ordered = this._getOrderedFor(document); - let provider: vscode.OnTypeFormattingEditProvider; - for (let entry of ordered) { - if (entry.triggerCharacters.indexOf(ch) >= 0) { - provider = entry.provider; - break; - } - } - - if (provider) { - return asWinJsPromise(token => provider.provideOnTypeFormattingEdits(document, pos, ch, options, token)).then(result => { - if (Array.isArray(result)) { - return result.map(ExtHostFormatDocument.convertTextEdit); - } - }); - } - } -} - -@Remotable.MainContext('MainThreadFormatOnType') -export class MainThreadFormatOnType extends AbstractMainThreadFeature implements modes.IFormattingSupport { - - autoFormatTriggerCharacters: string[] = []; - - constructor( @IThreadService threadService: IThreadService) { - super('vscode.executeFormatOnTypeProvider', FormatOnTypeRegistry, threadService); - } - - _register(selector: vscode.DocumentSelector, triggerCharacters: string[] = []): TPromise { - this.autoFormatTriggerCharacters.push(...triggerCharacters); - return super._register(selector); - } - - formatDocument(resource: URI, options: modes.IFormattingOptions):TPromise { - throw new Error('format on type only'); - } - - formatAfterKeystroke(resource: URI, position: IPosition, ch: string, options: modes.IFormattingOptions): TPromise { - return this._executeCommand(resource, position, ch, options); - } -} - -// ---- signature help - -export interface SignatureHelpEntry { - provider: vscode.SignatureHelpProvider; - triggerCharacters: string[]; -} - -export class ExtHostSignatureHelp extends AbstractExtensionHostFeature { - - constructor( @IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadSignatureHelp), threadService); - } - - register(selector: vscode.DocumentSelector, entry: SignatureHelpEntry): vscode.Disposable { - - let disposable = this._registry.register(selector, entry); - let registered = this._proxy._register(selector, entry.triggerCharacters); - - return new Disposable(() => { - disposable.dispose(); - registered.then(() => this._proxy._unregister()); - }); - } - - _runAsCommand(resource: URI, position: IPosition, triggerCharacter?: string): TPromise { - - let document = this._models.getDocument(resource); - let pos = TypeConverters.toPosition(position); - - let entry = this._getOrderedFor(document)[0]; - if (entry) { - - if (triggerCharacter) { - if (entry.triggerCharacters.indexOf(triggerCharacter) < 0) { - return; - } - } - - return asWinJsPromise(token => entry.provider.provideSignatureHelp(document, pos, token)).then(result => { - if (result instanceof SignatureHelp) { - return ExtHostSignatureHelp._convertSignatureHelp(result); - } - }); - } - } - - private static _convertSignatureHelp(signatureHelp: SignatureHelp): modes.IParameterHints { - - let result: modes.IParameterHints = { - currentSignature: signatureHelp.activeSignature, - currentParameter: signatureHelp.activeParameter, - signatures: [] - } - - for (let signature of signatureHelp.signatures) { - - let signatureItem: modes.ISignature = { - label: signature.label, - documentation: signature.documentation, - parameters: [] - }; - - let idx = 0; - for (let parameter of signature.parameters) { - - let parameterItem: modes.IParameter = { - label: parameter.label, - documentation: parameter.documentation, - }; - - signatureItem.parameters.push(parameterItem); - idx = signature.label.indexOf(parameter.label, idx); - - if (idx >= 0) { - parameterItem.signatureLabelOffset = idx; - idx += parameter.label.length; - parameterItem.signatureLabelEnd = idx; - } - } - - result.signatures.push(signatureItem); - } - - return result; - } -} - -@Remotable.MainContext('MainThreadSignatureHelp') -export class MainThreadSignatureHelp extends AbstractMainThreadFeature implements modes.IParameterHintsSupport { - - private _triggerCharacters: string[] = []; - - constructor( @IThreadService threadService: IThreadService) { - super('vscode.executeSignatureHelpProvider', ParameterHintsRegistry, threadService); - } - - _register(selector: vscode.DocumentSelector, triggerCharacters: string[] = []): TPromise { - this._triggerCharacters.push(...triggerCharacters); - return super._register(selector); - } - - getParameterHintsTriggerCharacters(): string[] { - return this._triggerCharacters; - } - - shouldTriggerParameterHints(context: modes.ILineContext, offset: number): boolean { - return true; - } - - getParameterHints(resource: URI, position: IPosition, triggerCharacter?: string): TPromise { - return this._executeCommand(resource, position, triggerCharacter); - } -} - -// ---- Completions - -export interface CompletionItemEnty { - provider: vscode.CompletionItemProvider; - triggerCharacters: string[]; -} - -@Remotable.PluginHostContext('ExtHostCompletions') -export class ExtHostCompletions extends AbstractExtensionHostFeature { - - private _detailsStorage: {[n:number]:[vscode.CompletionItemProvider, vscode.CompletionItem]} = Object.create(null); - - constructor( @IThreadService threadService: IThreadService) { - super(threadService.getRemotable(MainThreadCompletions), threadService); - } - - register(selector: vscode.DocumentSelector, entry: CompletionItemEnty): vscode.Disposable { - let disposable = this._registry.register(selector, entry); - let registered = this._proxy._register(selector, entry.triggerCharacters); - return new Disposable(() => { - disposable.dispose(); - registered.then(() => this._proxy._unregister()); - }); - } - - _runAsCommand(resource: URI, position: IPosition, character?: string): TPromise { - - this._detailsStorage = Object.create(null); - - let document = this._models.getDocument(resource); - let pos = TypeConverters.toPosition(position); - let ran = document.getWordRangeAtPosition(pos); - let entries = this._getOrderedFor(document); - - // filter - if (character) { - entries = entries.filter(provider => provider.triggerCharacters.indexOf(character) >= 0); - } - - let defaultSuggestions: modes.ISuggestions = { - suggestions: [], - currentWord: ran ? document.getText(new Range(ran.start, pos)) : '', - }; - let allSuggestions: modes.ISuggestions[] = [defaultSuggestions]; - - let promises = entries.map(entry => { - return asWinJsPromise(token => entry.provider.provideCompletionItems(document, pos, token)).then(result => { - if (!Array.isArray(result)) { - return; - } - - let canResolveDetails = typeof entry.provider.resolveCompletionItem === 'function'; - let detailsIdPool = 1; - - for (let item of result) { - - let suggestion = ExtHostCompletions._convertCompletionItem(item); - if (item.textEdit) { - - let editRange = item.textEdit.range; - - // invalid text edit - if (!editRange.isSingleLine || editRange.start.line !== pos.line) { - console.warn('INVALID text edit, must be single line and on the same line'); - continue; - } - - // insert the text of the edit and create a dedicated - // suggestion-container with overwrite[Before|After] - suggestion.codeSnippet = item.textEdit.newText; - - allSuggestions.push({ - currentWord: document.getText(editRange), - suggestions: [suggestion], - overwriteBefore: pos.character - editRange.start.character, - overwriteAfter: editRange.end.character - pos.character - }); - - } else { - defaultSuggestions.suggestions.push(suggestion); - } - - if (canResolveDetails) { - let id = detailsIdPool++; - (suggestion)._detailsId = id; - this._detailsStorage[id] = [entry.provider, item]; - } - } - }); - }); - - return TPromise.join(promises).then(() => allSuggestions); - } - - _resolveDetails(suggestion: modes.ISuggestion): TPromise { - let id = (suggestion)._detailsId; - if (this._detailsStorage[id]) { - let [provider, item] = this._detailsStorage[id]; - return asWinJsPromise(token => provider.resolveCompletionItem(item, token)).then(resolvedItem => { - return ExtHostCompletions._convertCompletionItem(resolvedItem || item); - }); - } - } - - private static _convertCompletionItem(item: vscode.CompletionItem): modes.ISuggestion { - return { - label: item.label, - codeSnippet: item.insertText || item.label, - type: CompletionItemKind[item.kind || CompletionItemKind.Text].toString().toLowerCase(), - typeLabel: item.detail, - documentationLabel: item.documentation, - sortText: item.sortText, - filterText: item.filterText - }; - } -} - -@Remotable.MainContext('MainThreadCompletions') -export class MainThreadCompletions extends AbstractMainThreadFeature { - - private _triggerCharacters: string[] = []; - private _proxy: ExtHostCompletions; - - constructor( @IThreadService threadService: IThreadService) { - super('vscode.executeCompletionItemProvider', SuggestRegistry, threadService); - this._proxy = threadService.getRemotable(ExtHostCompletions); - } - - _register(selector: vscode.DocumentSelector, triggerCharacters: string[] = []): TPromise { - this._triggerCharacters.push(...triggerCharacters); - return super._register(selector); - } - - suggest(resource: URI, position: IPosition, triggerCharacter?: string): TPromise { - return this._executeCommand(resource, position, triggerCharacter); - } - - getSuggestionDetails(resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise { - return this._proxy._resolveDetails(suggestion).then(value => { - return value || suggestion - }); - } - - getTriggerCharacters(): string[] { - return this._triggerCharacters; - } - - shouldShowEmptySuggestionList(): boolean { - return false; - } - - shouldAutotriggerSuggest(context: modes.ILineContext, offset: number, triggeredByCharacter: string): boolean { - return true; - } -} - -// ---- Code Lens - -@Remotable.PluginHostContext('ExtensionHostCodeLens') -export class ExtensionHostCodeLens extends AbstractExtensionHostFeature { - - 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 { - - 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 { - - if (!this._lenses[symbol.id]) { - return; - } - - let [, provider, lens] = this._lenses[symbol.id]; - let resolve: TPromise; - - 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: lens.command.command, - title: lens.command.title, - arguments: lens.command.arguments - } - } - }); - } -} - -@Remotable.MainContext('MainThreadCodeLens') -export class MainThreadCodeLens extends AbstractMainThreadFeature implements modes.ICodeLensSupport { - - private _proxy: ExtensionHostCodeLens; - - constructor( @IThreadService threadService: IThreadService) { - super('vscode.executeCodeLensProvider', CodeLensRegistry, threadService); - this._proxy = threadService.getRemotable(ExtensionHostCodeLens); - } - - findCodeLensSymbols(resource: URI): TPromise { - return this._executeCommand(resource); - } - - resolveCodeLensSymbol(resource: URI, symbol: modes.ICodeLensSymbol): TPromise { - return this._proxy._resolveCodeLensSymbol(symbol); - } -} - -// --- workspace symbols - -export class ExtensionHostWorkspaceSymbols { - - private _provider: vscode.WorkspaceSymbolProvider[] = []; - private _proxy: MainThreadWorkspaceSymbols; - private _threadService: IThreadService; - private _commands: PluginHostCommands; - - constructor(@IThreadService threadService: IThreadService) { - this._threadService = threadService; - this._commands = threadService.getRemotable(PluginHostCommands); - this._proxy = threadService.getRemotable(MainThreadWorkspaceSymbols); - this._commands.registerCommand(MainThreadWorkspaceSymbols.CommandId, this._runAsCommand, this); - } - - register(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { - - this._provider.push(provider); - - // is first, register commands, do stuff - if (this._provider.length === 1) { - this._proxy._enable(true); - } - - return new Disposable(() => { - let idx = this._provider.indexOf(provider); - if (idx >= 0) { - this._provider.splice(idx, 1); - if (this._provider.length === 0) { - this._proxy._enable(false); - } - } - }); - } - - private _runAsCommand(query: string): TPromise { - - if (typeof query !== 'string') { - return TPromise.wrapError('query is not string'); - } - - let symbols: vscode.SymbolInformation[] = []; - let promises = this._provider.map(provider => { - return asWinJsPromise(token => { - return provider.provideWorkspaceSymbols(query, token) - }).then(value => { - if (Array.isArray(value)) { - symbols.push(...value); - } - }, err => { - console.error(err); - }); - }); - - return TPromise.join(promises).then(() => { - return symbols.map(ExtensionHostWorkspaceSymbols._fromSymbolInformation); - }); - } - - private static _fromSymbolInformation(info: vscode.SymbolInformation): ITypeBearing { - return { - name: info.name, - type: SymbolKind[info.kind || SymbolKind.Property].toLowerCase(), - range: TypeConverters.fromRange(info.location.range), - resourceUri: info.location.uri, - containerName: info.containerName, - parameters: '', - }; - } -} - -@Remotable.MainContext('MainThreadWorkspaceSymbols') -export class MainThreadWorkspaceSymbols implements INavigateTypesSupport { - - static CommandId = 'vscode.executeWorkspaceSymbolProvider'; - - private _commands: PluginHostCommands; - private _disposable: IDisposable; - - constructor(@IThreadService threadService: IThreadService) { - this._commands = threadService.getRemotable(PluginHostCommands); - } - - _enable(value: boolean): void { - if (value) { - this._disposable = NavigateTypesSupportRegistry.register(this); - } else if (this._disposable) { - this._disposable.dispose(); - this._disposable = undefined; - } - } - - getNavigateToItems(search: string): TPromise { - let value = this._commands.executeCommand(MainThreadWorkspaceSymbols.CommandId, search); - return TPromise.as(value); - } -} - -export namespace LanguageFeatures { - - export function createMainThreadInstances(threadService: IThreadService): void { - threadService.getRemotable(MainThreadDefinitionFeature); - threadService.getRemotable(MainThreadHoverFeature); - threadService.getRemotable(MainThreadOccurrencesFeature); - threadService.getRemotable(MainThreadReferenceSearch); - threadService.getRemotable(MainThreadCodeActions); - threadService.getRemotable(MainThreadCodeLens); - threadService.getRemotable(MainThreadDocumentSymbols); - threadService.getRemotable(MainThreadWorkspaceSymbols); - threadService.getRemotable(MainThreadRename); - threadService.getRemotable(MainThreadFormatDocument); - threadService.getRemotable(MainThreadFormatRange); - threadService.getRemotable(MainThreadFormatOnType); - threadService.getRemotable(MainThreadSignatureHelp); - threadService.getRemotable(MainThreadCompletions); - } - - export function createExtensionHostInstances(threadService: IThreadService) { - return { - definition: new ExtensionHostDefinitionFeature(threadService), - hover: new ExtensionHostHoverFeature(threadService), - 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), - formatRange: new ExtHostFormatRange(threadService), - formatOnType: new ExtHostFormatOnType(threadService), - signatureHelp: new ExtHostSignatureHelp(threadService), - completions: threadService.getRemotable(ExtHostCompletions) - }; - } -} \ No newline at end of file diff --git a/src/vs/workbench/api/common/pluginHostTypeConverters.ts b/src/vs/workbench/api/common/pluginHostTypeConverters.ts index 74f3d9c5d9c..f87fa4d2617 100644 --- a/src/vs/workbench/api/common/pluginHostTypeConverters.ts +++ b/src/vs/workbench/api/common/pluginHostTypeConverters.ts @@ -8,7 +8,7 @@ import Severity from 'vs/base/common/severity'; import * as objects from 'vs/base/common/objects'; import {Position as EditorPosition} from 'vs/platform/editor/common/editor'; import {Selection, Range, Position, SymbolKind, DiagnosticSeverity, ViewColumn} from './pluginHostTypes'; -import {IPosition, ISelection, IRange, IRangeWithMessage} from 'vs/editor/common/editorCommon'; +import {IPosition, ISelection, IRange, IRangeWithMessage, ISingleEditOperation} from 'vs/editor/common/editorCommon'; import {IHTMLContentElement} from 'vs/base/common/htmlContent'; export interface PositionLike { @@ -189,3 +189,10 @@ export function fromRangeOrRangeWithMessage(ranges:vscode.Range[]|vscode.Decorat }); } } + +export function fromTextEdit(edit: vscode.TextEdit) { + return { + text: edit.newText, + range: fromRange(edit.range) + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/common/pluginHostTypes.ts b/src/vs/workbench/api/common/pluginHostTypes.ts index f8e6803369d..ab1850230f7 100644 --- a/src/vs/workbench/api/common/pluginHostTypes.ts +++ b/src/vs/workbench/api/common/pluginHostTypes.ts @@ -509,7 +509,7 @@ export class CodeLens { constructor(range: Range, command?: vscode.Command) { this.range = range; - this.command; + this.command = command; } get isResolved(): boolean { diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 6ba3ab87394..c3aee8b3eed 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -68,7 +68,7 @@ import {MainThreadLanguages} from 'vs/workbench/api/common/extHostLanguages'; import {MainThreadEditors} from 'vs/workbench/api/common/pluginHostEditors'; import {MainThreadWorkspace} from 'vs/workbench/api/browser/pluginHostWorkspace'; import {MainThreadConfiguration} from 'vs/workbench/api/common/pluginHostConfiguration'; -import {LanguageFeatures} from 'vs/workbench/api/common/languageFeatures'; +import {MainThreadLanguageFeatures} from 'vs/workbench/api/common/extHostLanguageFeatures'; import {EventService} from 'vs/platform/event/common/eventService'; import {IOptions} from 'vs/workbench/common/options'; import themes = require('vs/platform/theme/common/themes'); @@ -367,7 +367,7 @@ export class WorkbenchShell { this.threadService.getRemotable(MainThreadWorkspace); this.threadService.getRemotable(MainThreadEditors); this.threadService.getRemotable(MainThreadStorage); - LanguageFeatures.createMainThreadInstances(this.threadService); + this.threadService.getRemotable(MainThreadLanguageFeatures); } public open(): void { diff --git a/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts index 046dd4cc8d6..aa7743ebfdb 100644 --- a/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts @@ -31,7 +31,7 @@ import {IQuickOpenService} from 'vs/workbench/services/quickopen/browser/quickOp import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {Position} from 'vs/platform/editor/common/editor'; import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; -import QuickOpenRegistry from 'vs/editor/contrib/quickOpen/common/quickOpen'; +import {OutlineRegistry, getOutlineEntries} from 'vs/editor/contrib/quickOpen/common/quickOpen'; const ACTION_ID = 'workbench.action.gotoSymbol'; const ACTION_LABEL = nls.localize('gotoSymbol', "Go to Symbol..."); @@ -432,7 +432,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { if (model && types.isFunction((model).getMode)) { - canRun = QuickOpenRegistry.has( model); + canRun = OutlineRegistry.has( model); } } @@ -527,34 +527,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { return TPromise.as(this.outlineToModelCache[modelId]); } - let groupLabels: { [n: string]: string } = Object.create(null); - let entries: IOutlineEntry[] = []; - let resource = (model).getAssociatedResource(); - let promises = QuickOpenRegistry.all(model).map(support => { - - if (support.outlineGroupLabel) { - for (var key in support.outlineGroupLabel) { - if (Object.prototype.hasOwnProperty.call(support.outlineGroupLabel, key)) { - groupLabels[key] = support.outlineGroupLabel[key]; - } - } - } - - return support.getOutline(resource).then(result => { - if (Array.isArray(result)) { - entries.push(...result); - } - }, err => { - errors.onUnexpectedError(err); - }); - }); - - return TPromise.join(promises).then(() => { - - let outline = { - entries, - outlineGroupLabel: groupLabels - }; + return getOutlineEntries( model).then(outline => { let model = new OutlineModel(outline, this.toQuickOpenEntries(outline)); diff --git a/src/vs/workbench/parts/search/browser/openSymbolHandler.ts b/src/vs/workbench/parts/search/browser/openSymbolHandler.ts index 9ba6d686ae4..ce269924a21 100644 --- a/src/vs/workbench/parts/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/parts/search/browser/openSymbolHandler.ts @@ -23,7 +23,7 @@ import {IWorkbenchEditorService, IFileInput} from 'vs/workbench/services/editor/ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IModeService} from 'vs/editor/common/services/modeService'; -import {NavigateTypesSupportRegistry, ITypeBearing} from '../common/search'; +import {NavigateTypesSupportRegistry, ITypeBearing, getNavigateToItems} from '../common/search'; class SymbolEntry extends EditorQuickOpenEntry { private name: string; @@ -135,21 +135,7 @@ export class OpenSymbolHandler extends QuickOpenHandler { private doGetResults(searchValue: string): TPromise { - let registry = Registry.as(Extensions.EditorModes); - - // Find Types (and ignore error) - let bearings: ITypeBearing[] = []; - let promises = NavigateTypesSupportRegistry.getAll().map(support => { - return support.getNavigateToItems(searchValue).then(result => { - if (Array.isArray(result)) { - bearings.push(...result); - } - }, err => { - errors.onUnexpectedError(err); - }); - }); - - return TPromise.join(promises).then(() => { + return getNavigateToItems(searchValue).then(bearings => { return this.toQuickOpenEntries(bearings, searchValue); }); } diff --git a/src/vs/workbench/parts/search/common/search.ts b/src/vs/workbench/parts/search/common/search.ts index 92013490687..b62874ccf73 100644 --- a/src/vs/workbench/parts/search/common/search.ts +++ b/src/vs/workbench/parts/search/common/search.ts @@ -6,6 +6,7 @@ 'use strict'; import {TPromise} from 'vs/base/common/winjs.base'; +import {onUnexpectedError} from 'vs/base/common/errors'; import {IDisposable} from 'vs/base/common/lifecycle'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; import {IRange} from 'vs/editor/common/editorCommon'; @@ -51,11 +52,24 @@ export namespace NavigateTypesSupportRegistry { } } - // export function has(): boolean { - // return _supports.length > 0; - // } - - export function getAll(): INavigateTypesSupport[] { + export function all(): INavigateTypesSupport[] { return _supports.slice(0); } } + +export function getNavigateToItems(query: string): TPromise { + + const promises = NavigateTypesSupportRegistry.all().map(support => { + return support.getNavigateToItems(query).then(value => value, onUnexpectedError); + }); + + return TPromise.join(promises).then(all => { + const result: ITypeBearing[] = []; + for (let bearings of all) { + if (Array.isArray(bearings)) { + result.push(...bearings); + } + } + return result; + }); +} \ No newline at end of file diff --git a/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts new file mode 100644 index 00000000000..6c5073e6325 --- /dev/null +++ b/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts @@ -0,0 +1,804 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import {setUnexpectedErrorHandler, errorHandler} from 'vs/base/common/errors'; +import {create} from 'vs/base/common/types'; +import URI from 'vs/base/common/uri'; +import {URL} from 'vs/base/common/network'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {PluginHostDocument} from 'vs/workbench/api/common/pluginHostDocuments'; +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 {Model as EditorModel} from 'vs/editor/common/model/model'; +import threadService from './testThreadService' +import {create as createInstantiationService} from 'vs/platform/instantiation/common/instantiationService'; +import {MarkerService} from 'vs/platform/markers/common/markerService'; +import {IMarkerService} from 'vs/platform/markers/common/markers'; +import {IThreadService} from 'vs/platform/thread/common/thread'; +import {ExtHostLanguageFeatures, MainThreadLanguageFeatures} from 'vs/workbench/api/common/extHostLanguageFeatures'; +import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pluginHostCommands'; +import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments'; +import {SyncDescriptor0} from 'vs/platform/instantiation/common/descriptors'; +import {LanguageSelector, ModelLike} from 'vs/editor/common/modes/languageSelector'; +import {OutlineRegistry, getOutlineEntries} from 'vs/editor/contrib/quickOpen/common/quickOpen'; +import {CodeLensRegistry, getCodeLensData} from 'vs/editor/contrib/codelens/common/codelens'; +import {DeclarationRegistry, getDeclarationsAtPosition} from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; +import {ExtraInfoRegistry, getExtraInfoAtPosition} from 'vs/editor/contrib/hover/common/hover'; +import {OccurrencesRegistry, getOccurrencesAtPosition} from 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; +import {ReferenceRegistry, findReferences} from 'vs/editor/contrib/referenceSearch/common/referenceSearch'; +import {getQuickFixes} from 'vs/editor/contrib/quickFix/common/quickFix'; +import {getNavigateToItems} from 'vs/workbench/parts/search/common/search'; +import {rename} from 'vs/editor/contrib/rename/common/rename'; +import {getParameterHints} from 'vs/editor/contrib/parameterHints/common/parameterHints'; + +const defaultSelector = { scheme: 'far' }; +const model: EditorCommon.IModel = new EditorModel( + [ + 'This is the first line', + 'This is the second line', + 'This is the third line', + ].join('\n'), + undefined, + URL.fromUri(URI.parse('far://testing/file.a'))); + +let extHost: ExtHostLanguageFeatures; +let mainThread: MainThreadLanguageFeatures; +let disposables: vscode.Disposable[] = []; +let originalErrorHandler: (e: any) => any; + +suite('ExtHostLanguageFeatures', function() { + + suiteSetup(() => { + + let instantiationService = createInstantiationService(); + threadService.setInstantiationService(instantiationService); + instantiationService.addSingleton(IMarkerService, new MarkerService(threadService)); + instantiationService.addSingleton(IThreadService, threadService); + + originalErrorHandler = errorHandler.getUnexpectedErrorHandler(); + setUnexpectedErrorHandler(() => { }); + + threadService.getRemotable(PluginHostModelService)._acceptModelAdd({ + isDirty: false, + versionId: model.getVersionId(), + modeId: model.getModeId(), + url: model.getAssociatedResource(), + value: { + EOL: model.getEOL(), + lines: model.getValue().split(model.getEOL()), + BOM: '', + length: -1 + }, + }); + + threadService.getRemotable(PluginHostCommands); + threadService.getRemotable(MainThreadCommands); + mainThread = threadService.getRemotable(MainThreadLanguageFeatures); + extHost = threadService.getRemotable(ExtHostLanguageFeatures); + }); + + suiteTeardown(() => { + setUnexpectedErrorHandler(originalErrorHandler); + }); + + teardown(function(done) { + while (disposables.length) { + disposables.pop().dispose(); + } + threadService.sync() + .then(() => done(), err => done(err)); + }); + + // --- outline + + test('DocumentSymbols, register/deregister', function(done) { + assert.equal(OutlineRegistry.all(model).length, 0); + let d1 = extHost.registerDocumentSymbolProvider(defaultSelector, { + provideDocumentSymbols() { + return []; + } + }); + + threadService.sync().then(() => { + assert.equal(OutlineRegistry.all(model).length, 1); + d1.dispose(); + threadService.sync().then(() => { + done(); + }); + }); + + }); + + test('DocumentSymbols, evil provider', function(done) { + disposables.push(extHost.registerDocumentSymbolProvider(defaultSelector, { + provideDocumentSymbols(): any { + throw new Error('evil document symbol provider'); + } + })); + disposables.push(extHost.registerDocumentSymbolProvider(defaultSelector, { + provideDocumentSymbols(): any { + return [new types.SymbolInformation('test', types.SymbolKind.Field, new types.Range(0, 0, 0, 0))]; + } + })); + + threadService.sync().then(() => { + + getOutlineEntries(model).then(value => { + assert.equal(value.entries.length, 1); + done(); + }, err => { + done(err); + }); + }); + }); + + test('DocumentSymbols, data conversion', function(done) { + disposables.push(extHost.registerDocumentSymbolProvider(defaultSelector, { + provideDocumentSymbols(): any { + return [new types.SymbolInformation('test', types.SymbolKind.Field, new types.Range(0, 0, 0, 0))]; + } + })); + + threadService.sync().then(() => { + + getOutlineEntries(model).then(value => { + assert.equal(value.entries.length, 1); + + let entry = value.entries[0]; + assert.equal(entry.label, 'test'); + assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); + done(); + + }, err => { + done(err); + }); + }); + }); + + // --- code lens + + test('CodeLens, evil provider', function(done) { + + disposables.push(extHost.registerCodeLensProvider(defaultSelector, { + provideCodeLenses():any { + throw new Error('evil') + } + })); + disposables.push(extHost.registerCodeLensProvider(defaultSelector, { + provideCodeLenses() { + return [new types.CodeLens(new types.Range(0, 0, 0, 0))]; + } + })); + + threadService.sync().then(() => { + getCodeLensData(model).then(value => { + assert.equal(value.length, 1); + done(); + }); + }); + }); + + test('CodeLens, do not resolve a resolved lens', function(done) { + + disposables.push(extHost.registerCodeLensProvider(defaultSelector, { + 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).then(value => { + assert.equal(value.length, 1); + let data = value[0]; + + data.support.resolveCodeLensSymbol(model.getAssociatedResource(), 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(defaultSelector, { + provideCodeLenses() { + return [new types.CodeLens(new types.Range(0, 0, 0, 0))]; + } + })); + + threadService.sync().then(() => { + + getCodeLensData(model).then(value => { + assert.equal(value.length, 1); + + let data = value[0]; + data.support.resolveCodeLensSymbol(model.getAssociatedResource(), data.symbol).then(command => { + + assert.equal(command.id, 'missing'); + assert.equal(command.title, '<>'); + done(); + }); + }); + }); + }); + + // --- definition + + test('Definition, data conversion', function(done) { + + disposables.push(extHost.registerDefinitionProvider(defaultSelector, { + provideDefinition(): any { + return [new types.Location(model.getAssociatedResource(), new types.Range(1, 2, 3, 4))]; + } + })); + + threadService.sync().then(() => { + + getDeclarationsAtPosition(model, { lineNumber: 1, column: 1 }).then(value => { + assert.equal(value.length, 1); + let [entry] = value; + assert.deepEqual(entry.range, { startLineNumber: 2, startColumn: 3, endLineNumber: 4, endColumn: 5 }); + assert.equal(entry.resource.toString(), model.getAssociatedResource().toString()); + done(); + }, err => { + done(err); + }); + }); + }); + + test('Definition, one or many', function(done) { + + disposables.push(extHost.registerDefinitionProvider(defaultSelector, { + provideDefinition(): any { + return [new types.Location(model.getAssociatedResource(), new types.Range(1, 1, 1, 1))]; + } + })); + disposables.push(extHost.registerDefinitionProvider(defaultSelector, { + provideDefinition(): any { + return new types.Location(model.getAssociatedResource(), new types.Range(1, 1, 1, 1)); + } + })); + + threadService.sync().then(() => { + + getDeclarationsAtPosition(model, { lineNumber: 1, column: 1 }).then(value => { + assert.equal(value.length, 2); + done(); + }, err => { + done(err); + }); + }); + }); + + test('Definition, registration order', function(done) { + + disposables.push(extHost.registerDefinitionProvider(defaultSelector, { + provideDefinition(): any { + return [new types.Location(URI.parse('far://first'), new types.Range(2, 3, 4, 5))]; + } + })); + + setTimeout(function() { // registration time matters + disposables.push(extHost.registerDefinitionProvider(defaultSelector, { + provideDefinition(): any { + return new types.Location(URI.parse('far://second'), new types.Range(1, 2, 3, 4)); + } + })); + + threadService.sync().then(() => { + + getDeclarationsAtPosition(model, { lineNumber: 1, column: 1 }).then(value => { + assert.equal(value.length, 2); + // let [first, second] = value; + + assert.equal(value[0].resource.authority, 'second'); + assert.equal(value[1].resource.authority, 'first'); + done(); + + }, err => { + done(err); + }); + }); + }, 5); + }); + + test('Definition, evil provider', function(done) { + + disposables.push(extHost.registerDefinitionProvider(defaultSelector, { + provideDefinition(): any { + throw new Error('evil provider') + } + })); + disposables.push(extHost.registerDefinitionProvider(defaultSelector, { + provideDefinition(): any { + return new types.Location(model.getAssociatedResource(), new types.Range(1, 1, 1, 1)); + } + })); + + threadService.sync().then(() => { + + getDeclarationsAtPosition(model, { lineNumber: 1, column: 1 }).then(value => { + assert.equal(value.length, 1); + done(); + }, err => { + done(err); + }); + }); + }); + + // --- extra info + + test('ExtraInfo, word range at pos', function(done) { + + disposables.push(extHost.registerHoverProvider(defaultSelector, { + provideHover(): any { + return new types.Hover('Hello') + } + })); + + threadService.sync().then(() => { + + getExtraInfoAtPosition(model, { lineNumber: 1, column: 1 }).then(value => { + + assert.equal(value.length, 1); + let [entry] = value; + assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 }); + done(); + }); + }); + }); + + test('ExtraInfo, given range', function(done) { + + disposables.push(extHost.registerHoverProvider(defaultSelector, { + provideHover(): any { + return new types.Hover('Hello', new types.Range(3, 0, 8, 7)); + } + })); + + threadService.sync().then(() => { + + getExtraInfoAtPosition(model, { lineNumber: 1, column: 1 }).then(value => { + assert.equal(value.length, 1); + let [entry] = value; + assert.deepEqual(entry.range, { startLineNumber: 4, startColumn: 1, endLineNumber: 9, endColumn: 8 }); + done(); + }); + }); + }); + + test('ExtraInfo, registration order', function(done) { + + disposables.push(extHost.registerHoverProvider(defaultSelector, { + provideHover(): any { + return new types.Hover('registered first'); + } + })); + + setTimeout(function() { + disposables.push(extHost.registerHoverProvider(defaultSelector, { + provideHover(): any { + return new types.Hover('registered second'); + } + })); + + threadService.sync().then(() => { + + getExtraInfoAtPosition(model, { lineNumber: 1, column: 1 }).then(value => { + assert.equal(value.length, 2); + let [first, second] = value; + assert.equal(first.htmlContent[0].formattedText, 'registered second'); + assert.equal(second.htmlContent[0].formattedText, 'registered first'); + done(); + }); + }); + + }, 5); + + }); + + test('ExtraInfo, evil provider', function(done) { + + disposables.push(extHost.registerHoverProvider(defaultSelector, { + provideHover(): any { + throw new Error('evil') + } + })); + disposables.push(extHost.registerHoverProvider(defaultSelector, { + provideHover(): any { + return new types.Hover('Hello') + } + })); + + threadService.sync().then(() => { + + getExtraInfoAtPosition(model, { lineNumber: 1, column: 1 }).then(value => { + + assert.equal(value.length, 1); + done(); + }); + }); + }); + + // --- occurrences + + test('Occurrences, data conversion', function(done) { + + disposables.push(extHost.registerDocumentHighlightProvider(defaultSelector, { + provideDocumentHighlights(): any { + return [new types.DocumentHighlight(new types.Range(0, 0, 0, 4))] + } + })); + + threadService.sync().then(() => { + + getOccurrencesAtPosition(model, { lineNumber: 1, column: 2 }).then(value => { + assert.equal(value.length, 1); + let [entry] = value; + assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 }); + assert.equal(entry.kind, 'text'); + done(); + }); + }); + }); + + test('Occurrences, order 1/2', function(done) { + + disposables.push(extHost.registerDocumentHighlightProvider(defaultSelector, { + provideDocumentHighlights(): any { + return [] + } + })); + disposables.push(extHost.registerDocumentHighlightProvider('*', { + provideDocumentHighlights(): any { + return [new types.DocumentHighlight(new types.Range(0, 0, 0, 4))] + } + })); + + threadService.sync().then(() => { + + getOccurrencesAtPosition(model, { lineNumber: 1, column: 2 }).then(value => { + assert.equal(value.length, 1); + let [entry] = value; + assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 }); + assert.equal(entry.kind, 'text'); + done(); + }); + }); + }); + + test('Occurrences, order 2/2', function(done) { + + disposables.push(extHost.registerDocumentHighlightProvider(defaultSelector, { + provideDocumentHighlights(): any { + return [new types.DocumentHighlight(new types.Range(0, 0, 0, 2))] + } + })); + disposables.push(extHost.registerDocumentHighlightProvider('*', { + provideDocumentHighlights(): any { + return [new types.DocumentHighlight(new types.Range(0, 0, 0, 4))] + } + })); + + threadService.sync().then(() => { + + getOccurrencesAtPosition(model, { lineNumber: 1, column: 2 }).then(value => { + assert.equal(value.length, 1); + let [entry] = value; + assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 3 }); + assert.equal(entry.kind, 'text'); + done(); + }); + }); + }); + + test('Occurrences, evil provider', function(done) { + + disposables.push(extHost.registerDocumentHighlightProvider(defaultSelector, { + provideDocumentHighlights(): any { + throw new Error('evil'); + } + })); + + disposables.push(extHost.registerDocumentHighlightProvider(defaultSelector, { + provideDocumentHighlights(): any { + return [new types.DocumentHighlight(new types.Range(0, 0, 0, 4))] + } + })); + + threadService.sync().then(() => { + + getOccurrencesAtPosition(model, { lineNumber: 1, column: 2 }).then(value => { + assert.equal(value.length, 1); + done(); + }); + }); + }); + + // --- references + + test('References, registration order', function(done) { + + disposables.push(extHost.registerReferenceProvider(defaultSelector, { + provideReferences(): any { + return [new types.Location(URI.parse('far://register/first'), new types.Range(0, 0, 0, 0))]; + } + })); + + setTimeout(function() { + disposables.push(extHost.registerReferenceProvider(defaultSelector, { + provideReferences(): any { + return [new types.Location(URI.parse('far://register/second'), new types.Range(0, 0, 0, 0))]; + } + })); + + threadService.sync().then(() => { + + findReferences(model, { lineNumber: 1, column: 2 }).then(value => { + assert.equal(value.length, 2); + + let [first, second] = value; + assert.equal(first.resource.path, '/second'); + assert.equal(second.resource.path, '/first'); + done(); + }); + }); + }, 5); + }); + + test('References, data conversion', function(done) { + + disposables.push(extHost.registerReferenceProvider(defaultSelector, { + provideReferences(): any { + return [new types.Location(model.getAssociatedResource(), new types.Position(0, 0))]; + } + })); + + threadService.sync().then(() => { + + findReferences(model, { lineNumber: 1, column: 2 }).then(value => { + assert.equal(value.length, 1); + + let [item] = value; + assert.deepEqual(item.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); + assert.equal(item.resource.toString(), model.getAssociatedResource().toString()); + done(); + }); + + }); + }); + + test('References, evil provider', function(done) { + + disposables.push(extHost.registerReferenceProvider(defaultSelector, { + provideReferences(): any { + throw new Error('evil'); + } + })); + disposables.push(extHost.registerReferenceProvider(defaultSelector, { + provideReferences(): any { + return [new types.Location(model.getAssociatedResource(), new types.Range(0, 0, 0, 0))]; + } + })); + + threadService.sync().then(() => { + + findReferences(model, { lineNumber: 1, column: 2 }).then(value => { + assert.equal(value.length, 1); + done(); + }); + + }); + }); + + // --- quick fix + + test('Quick Fix, data conversion', function(done) { + + disposables.push(extHost.registerCodeActionProvider(defaultSelector, { + provideCodeActions(): any { + return [ + { command: 'test', title: 'Testing1' }, + { command: 'test', title: 'Testing2' } + ]; + } + })); + + threadService.sync().then(() => { + getQuickFixes(model, model.getFullModelRange()).then(value => { + assert.equal(value.length, 2); + + let [first, second] = value; + assert.equal(first.label, 'Testing1'); + assert.equal(first.id, String(0)); + assert.equal(second.label, 'Testing2'); + assert.equal(second.id, String(1)); + done(); + }); + }); + }); + + test('Quick Fix, invoke command+args', function(done) { + let actualArgs: any; + let commands = threadService.getRemotable(PluginHostCommands); + disposables.push(commands.registerCommand('test1', function(...args: any[]) { + actualArgs = args; + })); + + disposables.push(extHost.registerCodeActionProvider(defaultSelector, { + provideCodeActions(): any { + return [{ command: 'test1', title: 'Testing', arguments: [true, 1, { bar: 'boo', foo: 'far' }, null] }]; + } + })); + + threadService.sync().then(() => { + getQuickFixes(model, model.getFullModelRange()).then(value => { + assert.equal(value.length, 1); + + let [entry] = value; + entry.support.runQuickFixAction(model.getAssociatedResource(), model.getFullModelRange(), entry.id).then(value => { + assert.equal(value, undefined); + + assert.equal(actualArgs.length, 4); + assert.equal(actualArgs[0], true) + assert.equal(actualArgs[1], 1) + assert.deepEqual(actualArgs[2], { bar: 'boo', foo: 'far' }); + assert.equal(actualArgs[3], null) + done(); + }); + }); + }); + }); + + test('Quick Fix, evil provider', function(done) { + + disposables.push(extHost.registerCodeActionProvider(defaultSelector, { + provideCodeActions(): any { + throw new Error('evil'); + } + })); + disposables.push(extHost.registerCodeActionProvider(defaultSelector, { + provideCodeActions(): any { + return [{ command: 'test', title: 'Testing' }]; + } + })); + + threadService.sync().then(() => { + getQuickFixes(model, model.getFullModelRange()).then(value => { + assert.equal(value.length, 1); + done(); + }); + }); + }); + + // --- navigate types + + test('Navigate types, evil provider', function(done) { + + disposables.push(extHost.registerWorkspaceSymbolProvider({ + provideWorkspaceSymbols(): any { + throw new Error('evil'); + } + })); + + disposables.push(extHost.registerWorkspaceSymbolProvider({ + provideWorkspaceSymbols(): any { + return [new types.SymbolInformation('testing', types.SymbolKind.Array, new types.Range(0, 0, 1, 1))] + } + })); + + threadService.sync().then(() => { + + getNavigateToItems('').then(value => { + assert.equal(value.length, 1); + done(); + }); + }); + }); + + // --- rename + + test('Rename, evil provider 1/2', function(done) { + + disposables.push(extHost.registerRenameProvider(defaultSelector, { + provideRenameEdits(): any { + throw Error('evil'); + } + })); + + threadService.sync().then(() => { + + rename(model, { lineNumber: 1, column: 1 }, 'newName').then(value => { + done(new Error('')); + }, err => { + done(); // expected + }); + }); + }); + + test('Rename, evil provider 2/2', function(done) { + + disposables.push(extHost.registerRenameProvider('*', { + provideRenameEdits(): any { + throw Error('evil'); + } + })); + + disposables.push(extHost.registerRenameProvider(defaultSelector, { + provideRenameEdits(): any { + let edit = new types.WorkspaceEdit(); + edit.replace(model.getAssociatedResource(), new types.Range(0, 0, 0, 0), 'testing'); + return edit; + } + })); + + threadService.sync().then(() => { + + rename(model, { lineNumber: 1, column: 1 }, 'newName').then(value => { + assert.equal(value.edits.length, 1); + done(); + }); + }); + }); + + test('Rename, ordering', function(done) { + + disposables.push(extHost.registerRenameProvider('*', { + provideRenameEdits(): any { + let edit = new types.WorkspaceEdit(); + edit.replace(model.getAssociatedResource(), new types.Range(0, 0, 0, 0), 'testing'); + edit.replace(model.getAssociatedResource(), new types.Range(1, 0, 1, 0), 'testing'); + return edit; + } + })); + + disposables.push(extHost.registerRenameProvider(defaultSelector, { + provideRenameEdits(): any { + return; + } + })); + + threadService.sync().then(() => { + + rename(model, { lineNumber: 1, column: 1 }, 'newName').then(value => { + assert.equal(value.edits.length, 2); // least relevant renamer + done(); + }); + }); + }); + + // --- parameter hints + + test('Parameter Hints, evil provider', function(done) { + + disposables.push(extHost.registerSignatureHelpProvider(defaultSelector, { + provideSignatureHelp(): any { + throw new Error('evil'); + } + }, [])); + + threadService.sync().then(() => { + + getParameterHints(model, { lineNumber: 1, column: 1 }, '(').then(value => { + done(new Error('error expeted')); + }, err => { + assert.equal(err.message, 'evil'); + done(); + }) + }); + }) +}); \ No newline at end of file diff --git a/src/vs/workbench/test/common/api/languageFeatures.test.ts b/src/vs/workbench/test/common/api/languageFeatures.test.ts deleted file mode 100644 index 5ea693041f9..00000000000 --- a/src/vs/workbench/test/common/api/languageFeatures.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as assert from 'assert'; -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 {Range as CodeEditorRange} from 'vs/editor/common/core/range'; -import * as EditorCommon from 'vs/editor/common/editorCommon'; -import {NullThreadService} from 'vs/platform/test/common/nullThreadService' -import * as LF from 'vs/workbench/api/common/languageFeatures'; -import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pluginHostCommands'; -import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments'; -import {SyncDescriptor0} from 'vs/platform/instantiation/common/descriptors'; -import QuickOutlineRegistry from 'vs/editor/contrib/quickOpen/common/quickOpen'; -import {LanguageSelector, ModelLike} from 'vs/editor/common/modes/languageSelector'; - -class ThreadService extends NullThreadService { - - protected _registerAndInstantiateMainProcessActor(id: string, descriptor: SyncDescriptor0): T { - - let instance: any; - - return this._getOrCreateProxyInstance({ - callOnRemote: (proxyId: string, path: string, args: any[]): TPromise => { - if (!instance) { - instance = create(descriptor.ctor, this); - } - try { - let result = (instance[path]).apply(instance, args); - return TPromise.is(result) ? result : TPromise.as(result); - } catch (err) { - return TPromise.wrapError(err); - } - } - }, id, descriptor) - } - - protected _registerAndInstantiatePluginHostActor(id: string, descriptor: SyncDescriptor0): T { - return this._getOrCreateLocalInstance(id, descriptor); - } -} - -let threadService: ThreadService; -let model: ModelLike = { language: 'far', uri: URI.parse('far://testing/file.a') }; - -let extHost: LF.ExtensionHostDocumentSymbols; -let mainHost: LF.MainThreadDocumentSymbols; - -suite('ExtHostLanguageFeatures', function() { - - suiteSetup(() => { - threadService = new ThreadService(); - let documents = threadService.getRemotable(PluginHostModelService); - documents._acceptModelAdd({ - isDirty: false, - versionId: 1, - modeId: 'far', - url: model.uri, - value: { - EOL: '\n', - lines: [ - 'This is the first line', - 'This is the second line', - 'This is the third line', - ], - BOM: '', - length: -1 - }, - }) - threadService.getRemotable(PluginHostCommands); - threadService.getRemotable(MainThreadCommands); - threadService.getRemotable(LF.MainThreadDocumentSymbols); - extHost = new LF.ExtensionHostDocumentSymbols(threadService); - mainHost = threadService.getRemotable(LF.MainThreadDocumentSymbols); - }); - - test('DocumentSymbols, register/deregister', function() { - - - // register - assert.equal(QuickOutlineRegistry.all(model).length, 0); - let disposable = extHost.register('far', { - provideDocumentSymbols() { - return []; - } - }); - assert.equal(QuickOutlineRegistry.all(model).length, 1); - - // deregister - disposable.dispose(); - assert.equal(QuickOutlineRegistry.all(model).length, 0); - - // all extension host provider appear as one - disposable = extHost.register('far', { - provideDocumentSymbols() { - return []; - } - }); - let disposable2 = extHost.register('far', { - provideDocumentSymbols() { - return []; - } - }); - assert.equal(QuickOutlineRegistry.all(model).length, 1); - - disposable.dispose(); - assert.equal(QuickOutlineRegistry.all(model).length, 1); - disposable2.dispose(); - assert.equal(QuickOutlineRegistry.all(model).length, 0); - }); - - test('DocumentSymbols, evil provider', function(done) { - - - let disposable = extHost.register('far', { - provideDocumentSymbols():any { - throw new Error('ddd'); - } - }); - let disposable2 = extHost.register('far', { - provideDocumentSymbols():any { - return [ - new phTypes.SymbolInformation('boo', phTypes.SymbolKind.Field, new phTypes.Range(0, 0, 0, 0)) - ]; - } - }); - - mainHost.getOutline(model.uri).then(result => { - assert.equal(result.length, 1); - done(); - - disposable.dispose(); - disposable2.dispose(); - - }, err => { - done(err); - }); - }); - - test('DocumentSymbols, data conversion', function(done) { - - let d = extHost.register('far', { - provideDocumentSymbols():any { - return [ - new phTypes.SymbolInformation('boo', - phTypes.SymbolKind.Field, - new phTypes.Range(0, 0, 0, 0), - model.uri, - 'far') - ]; - } - }); - - mainHost.getOutline(model.uri).then(result => { - assert.equal(result.length, 1); - let entry = result[0]; - - assert.equal(entry.label, 'boo'); - assert.equal(entry.containerLabel, 'far'); - assert.equal(entry.children, undefined); - assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); - d.dispose(); - done(); - - }, err => { - done(err); - }); - - }); -}); \ No newline at end of file diff --git a/src/vs/workbench/test/common/api/testThreadService.ts b/src/vs/workbench/test/common/api/testThreadService.ts new file mode 100644 index 00000000000..6bf5118e96a --- /dev/null +++ b/src/vs/workbench/test/common/api/testThreadService.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import {NullThreadService} from 'vs/platform/test/common/nullThreadService'; +import {create} from 'vs/base/common/types'; +import {SyncDescriptor0} from 'vs/platform/instantiation/common/descriptors'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; + +export class TestThreadService extends NullThreadService { + + private _callCountValue: number = 0; + private _idle: TPromise; + private _completeIdle: Function; + + private get _callCount(): number { + return this._callCountValue; + } + + private set _callCount(value:number) { + this._callCountValue = value; + if (this._callCountValue === 0) { + this._completeIdle(); + this._idle = undefined; + } + } + + sync(): TPromise { + if (this._callCount === 0) { + return TPromise.as(undefined); + } + if (!this._idle) { + this._idle = new TPromise((c, e) => { + this._completeIdle = c; + }, function() { + // no cancel + }); + } + return this._idle; + } + + protected _registerAndInstantiateMainProcessActor(id: string, descriptor: SyncDescriptor0): T { + + let _calls:{path: string; args: any[] }[] = []; + let _instance: any; + + return this._getOrCreateProxyInstance({ + + + callOnRemote: (proxyId: string, path: string, args: any[]): TPromise => { + + this._callCount++; + _calls.push({path, args}); + + return TPromise.timeout(0).then(() => { + if (!_instance) { + _instance = this._instantiationService.createInstance(descriptor.ctor); + } + let p: TPromise; + try { + let {path, args} = _calls.shift(); + let result = (_instance[path]).apply(_instance, args); + p = TPromise.is(result) ? result : TPromise.as(result); + } catch (err) { + p = TPromise.wrapError(err); + } + + return p.then(result => { + this._callCount--; + return result; + }, err => { + this._callCount--; + return TPromise.wrapError(err); + }); + }); + } + }, id, descriptor) + } + + protected _registerAndInstantiatePluginHostActor(id: string, descriptor: SyncDescriptor0): T { + return this._getOrCreateLocalInstance(id, descriptor); + } +} + +const Instance = new TestThreadService(); +export default Instance; \ No newline at end of file