提交 c9fd775d 编写于 作者: J Johannes Rieken

code action provider as support

上级 fc95d2a7
......@@ -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 {
......
......@@ -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;
}
......
......@@ -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<IQuickFixSupport>('quickFixSupport');
export default QuickFixRegistry;
export const QuickFixRegistry = new LanguageFeatureRegistry<IQuickFixSupport>('quickFixSupport');
export interface IQuickFix2 extends IQuickFix {
support: IQuickFixSupport;
}
export function getQuickFixes(model: IModel, range: IRange): TPromise<IQuickFix2[]> {
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
......@@ -5,8 +5,7 @@
'use strict';
import {IReferenceSupport} from 'vs/editor/common/modes';
import {IReference} 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';
......
......@@ -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<any>;};
_pendingObjects:winjs.Promise[];
private _localObjMap: { [id:string]: any; };
......
......@@ -265,7 +265,7 @@ export class PluginHostAPIImplementation {
return score(selector, { uri: <any> 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 languageFeatures.registerCodeLensProvider(selector, provider);
......
......@@ -27,7 +27,7 @@ import {DeclarationRegistry} from 'vs/editor/contrib/goToDeclaration/common/goTo
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 {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'
......@@ -295,7 +295,68 @@ class ReferenceAdapter implements modes.IReferenceSupport {
}
}
type Adapter = OutlineAdapter | CodeLensAdapter | DeclarationAdapter | ExtraInfoAdapter | OccurrencesAdapter | ReferenceAdapter;
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<modes.IQuickFix[]> {
// 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: <any>diagnostics }, token)).then(commands => {
if (!Array.isArray(commands)) {
return;
}
this._cache[key] = commands;
return commands.map((command, i) => {
return <modes.IQuickFix> {
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);
}
}
type Adapter = OutlineAdapter | CodeLensAdapter | DeclarationAdapter | ExtraInfoAdapter | OccurrencesAdapter | ReferenceAdapter | QuickFixAdapter;
@Remotable.PluginHostContext('ExtHostLanguageFeatures')
export class ExtHostLanguageFeatures {
......@@ -304,11 +365,13 @@ export class ExtHostLanguageFeatures {
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 {
......@@ -412,16 +475,34 @@ export class ExtHostLanguageFeatures {
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<modes.IQuickFix[]> {
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));
}
}
@Remotable.MainContext('MainThreadLanguageFeatures')
export class MainThreadLanguageFeatures {
private _proxy: ExtHostLanguageFeatures;
private _markerService: IMarkerService;
private _registrations: { [handle: number]: IDisposable; } = Object.create(null);
constructor( @IThreadService threadService: IThreadService) {
constructor( @IThreadService threadService: IThreadService, @IMarkerService markerService: IMarkerService) {
this._proxy = threadService.getRemotable(ExtHostLanguageFeatures);
this._markerService = markerService;
}
$unregister(handle: number): TPromise<any> {
......@@ -507,4 +588,24 @@ export class MainThreadLanguageFeatures {
});
return undefined;
}
// --- quick fix
$registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
this._registrations[handle] = QuickFixRegistry.register(selector, <modes.IQuickFixSupport>{
getQuickFixes: (resource: URI, range: IRange): TPromise<modes.IQuickFix[]> => {
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;
}
}
\ No newline at end of file
......@@ -22,7 +22,6 @@ 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 QuickFixRegistry from 'vs/editor/contrib/quickFix/common/quickFix';
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';
......@@ -131,104 +130,6 @@ export abstract class AbstractExtensionHostFeature<T, P extends AbstractMainThre
}
}
// --- quick fix aka code actions
export class ExtensionHostCodeActions extends AbstractExtensionHostFeature<vscode.CodeActionProvider, MainThreadCodeActions> {
private _disposable: Disposable;
constructor(@IThreadService threadService: IThreadService) {
super(threadService.getRemotable(MainThreadCodeActions), threadService);
}
_runAsCommand(resource: URI, range: IRange, marker:IMarker[]): TPromise<modes.IQuickFix[]> {
let document = this._models.getDocument(resource);
let _range = TypeConverters.toRange(range);
let commands: vscode.Command[] = [];
let diagnostics = <vscode.Diagnostic[]> <any> 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<modes.IQuickFixSupport> 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<modes.IQuickFix[]> {
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));
}
}
// -- Rename provider
......@@ -826,7 +727,6 @@ export class MainThreadWorkspaceSymbols implements INavigateTypesSupport {
export namespace LanguageFeatures {
export function createMainThreadInstances(threadService: IThreadService): void {
threadService.getRemotable(MainThreadCodeActions);
threadService.getRemotable(MainThreadWorkspaceSymbols);
threadService.getRemotable(MainThreadRename);
threadService.getRemotable(MainThreadFormatDocument);
......@@ -838,7 +738,6 @@ export namespace LanguageFeatures {
export function createExtensionHostInstances(threadService: IThreadService) {
return {
codeActions: new ExtensionHostCodeActions(threadService),
workspaceSymbols: new ExtensionHostWorkspaceSymbols(threadService),
rename: new ExtensionHostRename(threadService),
formatDocument: new ExtHostFormatDocument(threadService),
......
......@@ -17,6 +17,10 @@ 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';
......@@ -28,6 +32,7 @@ import {DeclarationRegistry, getDeclarationsAtPosition} from 'vs/editor/contrib/
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';
const defaultSelector = { scheme: 'far' };
const model: EditorCommon.IModel = new EditorModel(
......@@ -48,6 +53,11 @@ suite('ExtHostLanguageFeatures', function() {
suiteSetup(() => {
let instantiationService = createInstantiationService();
threadService.setInstantiationService(instantiationService);
instantiationService.addSingleton(IMarkerService, new MarkerService(threadService));
instantiationService.addSingleton(IThreadService, threadService);
originalErrorHandler = errorHandler.getUnexpectedErrorHandler();
setUnexpectedErrorHandler(() => { });
......@@ -591,4 +601,84 @@ suite('ExtHostLanguageFeatures', function() {
});
});
// --- quick fix
test('Quick Fix, data conversion', function(done) {
disposables.push(extHost.registerCodeActionProvider(defaultSelector, <vscode.CodeActionProvider>{
provideCodeActions(): any {
return [
<vscode.Command>{ command: 'test', title: 'Testing1' },
<vscode.Command>{ 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, <vscode.CodeActionProvider>{
provideCodeActions(): any {
return [<vscode.Command>{ 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, <vscode.CodeActionProvider>{
provideCodeActions(): any {
throw new Error('evil');
}
}));
disposables.push(extHost.registerCodeActionProvider(defaultSelector, <vscode.CodeActionProvider>{
provideCodeActions(): any {
return [<vscode.Command>{ command: 'test', title: 'Testing' }];
}
}));
threadService.sync().then(() => {
getQuickFixes(model, model.getFullModelRange()).then(value => {
assert.equal(value.length, 1);
done();
});
});
});
});
\ No newline at end of file
......@@ -9,6 +9,7 @@ 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 {
......@@ -57,7 +58,7 @@ export class TestThreadService extends NullThreadService {
return TPromise.timeout(0).then(() => {
if (!_instance) {
_instance = create(descriptor.ctor, this);
_instance = this._instantiationService.createInstance(descriptor.ctor);
}
let p: TPromise<any>;
try {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册