From e71d1e88412352e1b2bd653eacf2c7c731b9ac98 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 22 Sep 2016 13:49:25 +0200 Subject: [PATCH] first cut of format on save --- src/vs/editor/contrib/format/common/format.ts | 28 +++++- .../api/node/mainThreadSaveParticipant.ts | 98 +++++++++++-------- 2 files changed, 86 insertions(+), 40 deletions(-) diff --git a/src/vs/editor/contrib/format/common/format.ts b/src/vs/editor/contrib/format/common/format.ts index dd0530ba182..876c06028ad 100644 --- a/src/vs/editor/contrib/format/common/format.ts +++ b/src/vs/editor/contrib/format/common/format.ts @@ -9,12 +9,13 @@ import {illegalArgument} from 'vs/base/common/errors'; import URI from 'vs/base/common/uri'; import {TPromise} from 'vs/base/common/winjs.base'; import {Range} from 'vs/editor/common/core/range'; -import {IReadOnlyModel, ISingleEditOperation} from 'vs/editor/common/editorCommon'; +import {IReadOnlyModel, ISingleEditOperation, IModel, ICommonCodeEditor} from 'vs/editor/common/editorCommon'; import {CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; import {DocumentFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry, OnTypeFormattingEditProviderRegistry, FormattingOptions} from 'vs/editor/common/modes'; import {IModelService} from 'vs/editor/common/services/modelService'; import {asWinJsPromise} from 'vs/base/common/async'; import {Position} from 'vs/editor/common/core/position'; +import {EditOperationsCommand} from './formatCommand'; export function getDocumentRangeFormattingEdits(model: IReadOnlyModel, range: Range, options: FormattingOptions): TPromise { const [support] = DocumentRangeFormattingEditProviderRegistry.ordered(model); @@ -85,3 +86,28 @@ CommonEditorRegistry.registerDefaultLanguageCommand('_executeFormatOnTypeProvide } return getOnTypeFormattingEdits(model, position, ch, options); }); + + +export function formatDocument(model: IModel, editor: ICommonCodeEditor) { + + const {tabSize, insertSpaces} = model.getOptions(); + + return getDocumentRangeFormattingEdits(model, model.getFullModelRange(), { tabSize, insertSpaces }).then(edits => { + if (!edits) { + return; + } + + if (!editor) { + + model.applyEdits(edits.map(({text, range}) => ({ + text, + range: Range.lift(range), + identifier: undefined, + forceMoveMarkers: true + }))); + + } else { + editor.executeCommand('format.document', new EditOperationsCommand(edits, editor.getSelection())); + } + }); +}; diff --git a/src/vs/workbench/api/node/mainThreadSaveParticipant.ts b/src/vs/workbench/api/node/mainThreadSaveParticipant.ts index c6af4737f32..ca56d6ac3c8 100644 --- a/src/vs/workbench/api/node/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/node/mainThreadSaveParticipant.ts @@ -5,47 +5,30 @@ 'use strict'; -import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {TPromise} from 'vs/base/common/winjs.base'; import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; import {ISaveParticipant, ITextFileEditorModel} from 'vs/workbench/parts/files/common/files'; -import {IFilesConfiguration} from 'vs/platform/files/common/files'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IPosition, IModel} from 'vs/editor/common/editorCommon'; import {Selection} from 'vs/editor/common/core/selection'; import {trimTrailingWhitespace} from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; +import {formatDocument} from 'vs/editor/contrib/format/common/format'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; import {ExtHostContext, ExtHostDocumentSaveParticipantShape} from './extHost.protocol'; -// TODO@joh move this to the extension host -class TrimWhitespaceParticipant { - - private trimTrailingWhitespace: boolean = false; - private toUnbind: IDisposable[] = []; +class TrimWhitespaceParticipant implements ISaveParticipant { constructor( @IConfigurationService private configurationService: IConfigurationService, @ICodeEditorService private codeEditorService: ICodeEditorService ) { - this.registerListeners(); - this.onConfigurationChange(this.configurationService.getConfiguration()); - } - - public dispose(): void { - this.toUnbind = dispose(this.toUnbind); - } - - private registerListeners(): void { - this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config))); - } - - private onConfigurationChange(configuration: IFilesConfiguration): void { - this.trimTrailingWhitespace = configuration && configuration.files && configuration.files.trimTrailingWhitespace; + // Nothing } public participate(model: ITextFileEditorModel, env: { isAutoSaved: boolean }): any { - if (this.trimTrailingWhitespace) { + if (this.configurationService.lookup('files.trimTrailingWhitespace').value) { this.doTrimTrailingWhitespace(model.textEditorModel, env.isAutoSaved); } } @@ -89,34 +72,71 @@ class TrimWhitespaceParticipant { } } +class FormatOnSaveParticipant implements ISaveParticipant { + + constructor( + @ICodeEditorService private _editorService: ICodeEditorService, + @IConfigurationService private _configurationService: IConfigurationService + ) { + // Nothing + } + + participate(editorModel: ITextFileEditorModel, env: { isAutoSaved: boolean }): TPromise { + if (true || this._configurationService.lookup('files.formatOnSave').value) { + const model: IModel = editorModel.textEditorModel; + const editor = this._findEditor(model); + return formatDocument(model, editor); + } + } + + private _findEditor(model: IModel) { + for (const editor of this._editorService.listCodeEditors()) { + if (editor.getModel() === model) { + return editor; + } + } + } +} + +class ExtHostSaveParticipant implements ISaveParticipant { + + private _proxy: ExtHostDocumentSaveParticipantShape; + + constructor( @IThreadService threadService: IThreadService) { + this._proxy = threadService.get(ExtHostContext.ExtHostDocumentSaveParticipant); + } + + participate(editorModel: ITextFileEditorModel, env: { isAutoSaved: boolean }): TPromise { + return this._proxy.$participateInSave(editorModel.getResource()); + } +} + // The save participant can change a model before its saved to support various scenarios like trimming trailing whitespace export class SaveParticipant implements ISaveParticipant { - private _mainThreadSaveParticipant: TrimWhitespaceParticipant; - private _extHostSaveParticipant: ExtHostDocumentSaveParticipantShape; + private _saveParticipants: ISaveParticipant[]; constructor( - @IConfigurationService configurationService: IConfigurationService, - @ICodeEditorService codeEditorService: ICodeEditorService, + @IInstantiationService instantiationService: IInstantiationService, @IThreadService threadService: IThreadService ) { - this._mainThreadSaveParticipant = new TrimWhitespaceParticipant(configurationService, codeEditorService); - this._extHostSaveParticipant = threadService.get(ExtHostContext.ExtHostDocumentSaveParticipant); + + this._saveParticipants = [ + instantiationService.createInstance(TrimWhitespaceParticipant), + instantiationService.createInstance(FormatOnSaveParticipant), + instantiationService.createInstance(ExtHostSaveParticipant) + ]; // Hook into model TextFileEditorModel.setSaveParticipant(this); } - - dispose() { - this._mainThreadSaveParticipant.dispose(); - } - participate(model: ITextFileEditorModel, env: { isAutoSaved: boolean }): TPromise { - try { - this._mainThreadSaveParticipant.participate(model, env); - } catch (err) { - // ignore + const promises: TPromise[] = []; + for (const participant of this._saveParticipants) { + promises.push(TPromise.as(participant.participate(model, env)).then(undefined, err => { + console.error(err); + })); } - return this._extHostSaveParticipant.$participateInSave(model.getResource()); + return TPromise.join(promises); } -} \ No newline at end of file +}