From 78e11f23b7ddfaa46e417a2a72ca1984b5cac0cc Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 1 Dec 2015 16:38:42 +0100 Subject: [PATCH] commands: executeFormatDocumentProvider, executeFormatRangeProvider --- src/vs/editor/contrib/format/common/format.ts | 61 +++++++++++++++++- .../contrib/format/common/formatActions.ts | 36 ++++------- .../common/extHostLanguageFeatureCommands.ts | 34 ++++++++-- .../api/extHostLanguageFeatures.test.ts | 63 +++++++++++++++++++ 4 files changed, 163 insertions(+), 31 deletions(-) diff --git a/src/vs/editor/contrib/format/common/format.ts b/src/vs/editor/contrib/format/common/format.ts index 99b89717af0..d0889262cfb 100644 --- a/src/vs/editor/contrib/format/common/format.ts +++ b/src/vs/editor/contrib/format/common/format.ts @@ -5,10 +5,67 @@ 'use strict'; -import {IFormattingSupport} from 'vs/editor/common/modes'; +import {IFormattingSupport, IFormattingOptions} from 'vs/editor/common/modes'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; +import {onUnexpectedError, illegalArgument} from 'vs/base/common/errors'; +import URI from 'vs/base/common/uri'; +import {IAction, Action} from 'vs/base/common/actions'; +import {IModelService} from 'vs/editor/common/services/modelService'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {IModel, IRange, IPosition, ISingleEditOperation} from 'vs/editor/common/editorCommon'; +import {Range} from 'vs/editor/common/core/range'; +import {CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; export const FormatRegistry = new LanguageFeatureRegistry('formattingSupport'); export const FormatOnTypeRegistry = new LanguageFeatureRegistry('formattingSupport'); -export {IFormattingSupport}; \ No newline at end of file +export {IFormattingSupport}; + +export function formatRange(model: IModel, range: IRange, options: IFormattingOptions): TPromise { + const support = FormatRegistry.ordered(model)[0]; + if (!support) { + return; + } + return support.formatRange(model.getAssociatedResource(), range, options); +} + +export function formatDocument(model: IModel, options: IFormattingOptions): TPromise { + const support = FormatRegistry.ordered(model)[0]; + if (!support) { + return; + } + if (typeof support.formatDocument !== 'function') { + if (typeof support.formatRange === 'function') { + return formatRange(model, model.getFullModelRange(), options); + } else { + return; + } + } + + return support.formatDocument(model.getAssociatedResource(), options); +} + +CommonEditorRegistry.registerLanguageCommand('_executeFormatRangeProvider', function(accessor, args) { + const {resource, range, options} = args; + if (!URI.isURI(resource) || !Range.isIRange(range)) { + throw illegalArgument(); + } + const model = accessor.get(IModelService).getModel(resource); + if (!model) { + throw illegalArgument('resource'); + } + return formatRange(model, range, options); +}); + +CommonEditorRegistry.registerLanguageCommand('_executeFormatDocumentProvider', function(accessor, args) { + const {resource, options} = args; + if (!URI.isURI(resource)) { + throw illegalArgument('resource'); + } + const model = accessor.get(IModelService).getModel(resource); + if (!model) { + throw illegalArgument('resource'); + } + + return formatDocument(model, options) +}); \ No newline at end of file diff --git a/src/vs/editor/contrib/format/common/formatActions.ts b/src/vs/editor/contrib/format/common/formatActions.ts index 83ddd947b62..25e5c5fce8f 100644 --- a/src/vs/editor/contrib/format/common/formatActions.ts +++ b/src/vs/editor/contrib/format/common/formatActions.ts @@ -15,7 +15,7 @@ import formatCommand = require('./formatCommand'); import {Range} from 'vs/editor/common/core/range'; import {INullService} from 'vs/platform/instantiation/common/instantiation'; import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; -import {FormatOnTypeRegistry, FormatRegistry, IFormattingSupport} from '../common/format'; +import {FormatOnTypeRegistry, FormatRegistry, IFormattingSupport, formatRange, formatDocument} from '../common/format'; interface IFormatOnTypeResult { range: EditorCommon.IEditorRange; @@ -170,31 +170,19 @@ export class FormatAction extends EditorAction { public run(): TPromise { - var model = this.editor.getModel(), - formattingSupport = FormatRegistry.ordered(model)[0], - canFormatRange = typeof formattingSupport.formatRange === 'function', - canFormatDocument = typeof formattingSupport.formatDocument === 'function', - editorSelection = this.editor.getSelection(); - - var options = this.editor.getIndentationOptions(), - formattingPromise: TPromise; - - if(canFormatRange) { - // format a selection/range - var formatRange: EditorCommon.IEditorRange = editorSelection; - if(!formatRange.isEmpty()) { - // Fix the selection to include the entire line to improve formatting results - formatRange.startColumn = 1; - } else { - formatRange = model.getFullModelRange(); - } - formattingPromise = formattingSupport.formatRange(model.getAssociatedResource(), formatRange, options); + const model = this.editor.getModel(), + editorSelection = this.editor.getSelection(), + options = this.editor.getIndentationOptions(); + + let formattingPromise: TPromise; - } else if(canFormatDocument) { - // format the whole document - formattingPromise = formattingSupport.formatDocument(model.getAssociatedResource(), options); + if (editorSelection.isEmpty()) { + formattingPromise = formatDocument(model, options); } else { - // broken support? + formattingPromise = formatRange(model, editorSelection, options); + } + + if (!formattingPromise) { return TPromise.as(false); } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatureCommands.ts b/src/vs/workbench/api/common/extHostLanguageFeatureCommands.ts index 473b820863b..15cb18343b3 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatureCommands.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatureCommands.ts @@ -48,9 +48,9 @@ import {ICodeLensData} from 'vs/editor/contrib/codelens/common/codelens'; // vscode.executeCompletionItemProvider // vscode.executeCodeActionProvider // vscode.executeCodeLensProvider - // vscode.executeFormatDocumentProvider // vscode.executeFormatRangeProvider + // vscode.executeFormatOnTypeProvider export class ExtHostLanguageFeatureCommands { @@ -72,6 +72,8 @@ export class ExtHostLanguageFeatureCommands { this._register('vscode.executeCompletionItemProvider', this._executeCompletionItemProvider); this._register('vscode.executeCodeActionProvider', this._executeCodeActionProvider); this._register('vscode.executeCodeLensProvider', this._executeCodeLensProvider); + this._register('vscode.executeFormatDocumentProvider', this._executeFormatDocumentProvider); + this._register('vscode.executeFormatRangeProvider', this._executeFormatRangeProvider); } private _register(id: string, callback: (...args: any[]) => any): void { @@ -217,10 +219,7 @@ export class ExtHostLanguageFeatureCommands { } private _executeCodeLensProvider(resource: URI): Thenable{ - const args = { - resource - }; - + const args = { resource }; return this._commands.executeCommand('_executeCodeLensProvider', args).then(value => { if (Array.isArray(value)) { return value.map(item => { @@ -230,4 +229,29 @@ export class ExtHostLanguageFeatureCommands { } }); } + + private _executeFormatDocumentProvider(resource: URI, options: vscode.FormattingOptions): Thenable { + const args = { + resource, + options + }; + return this._commands.executeCommand('_executeFormatDocumentProvider', args).then(value => { + if (Array.isArray(value)) { + return value.map(edit => new types.TextEdit(typeConverters.toRange(edit.range), edit.text)); + } + }); + } + + private _executeFormatRangeProvider(resource: URI, range: types.Range, options: vscode.FormattingOptions): Thenable { + const args = { + resource, + range: typeConverters.fromRange(range), + options + }; + return this._commands.executeCommand('_executeFormatRangeProvider', args).then(value => { + if (Array.isArray(value)) { + return value.map(edit => new types.TextEdit(typeConverters.toRange(edit.range), edit.text)); + } + }); + } } \ 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 index 5a9710e0e96..4af861e6a00 100644 --- a/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/common/api/extHostLanguageFeatures.test.ts @@ -37,6 +37,7 @@ 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'; import {suggest} from 'vs/editor/contrib/suggest/common/suggest'; +import {formatDocument, formatRange} from 'vs/editor/contrib/format/common/format'; const defaultSelector = { scheme: 'far' }; const model: EditorCommon.IModel = new EditorModel( @@ -907,4 +908,66 @@ suite('ExtHostLanguageFeatures', function() { }); }); }); + + // --- format + + test('Format Doc, data conversion', function(done) { + disposables.push(extHost.registerDocumentFormattingEditProvider(defaultSelector, { + provideDocumentFormattingEdits(): any { + return [new types.TextEdit(new types.Range(0, 0, 1, 1), 'testing')]; + } + })); + + threadService.sync().then(() => { + formatDocument(model, { insertSpaces: true, tabSize: 4 }).then(value => { + assert.equal(value.length, 1); + let [first] = value; + assert.equal(first.text, 'testing'); + assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 }); + done(); + }); + }); + }); + + test('Format Doc, evil provider', function(done) { + disposables.push(extHost.registerDocumentFormattingEditProvider(defaultSelector, { + provideDocumentFormattingEdits(): any { + throw new Error('evil'); + } + })); + + threadService.sync().then(() => { + formatDocument(model, { insertSpaces: true, tabSize: 4 }).then(undefined, err => done()); + }); + }); + + test('Format Range, data conversion', function(done) { + disposables.push(extHost.registerDocumentRangeFormattingEditProvider(defaultSelector, { + provideDocumentRangeFormattingEdits(): any { + return [new types.TextEdit(new types.Range(0, 0, 1, 1), 'testing')]; + } + })); + + threadService.sync().then(() => { + formatRange(model, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, { insertSpaces: true, tabSize: 4 }).then(value => { + assert.equal(value.length, 1); + let [first] = value; + assert.equal(first.text, 'testing'); + assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 }); + done(); + }); + }); + }) + + test('Format Range, evil provider', function(done) { + disposables.push(extHost.registerDocumentRangeFormattingEditProvider(defaultSelector, { + provideDocumentRangeFormattingEdits(): any { + throw new Error('evil'); + } + })); + + threadService.sync().then(() => { + formatRange(model, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, { insertSpaces: true, tabSize: 4 }).then(undefined, err => done()); + }); + }) }); \ No newline at end of file -- GitLab