From 2f98e45e8db46e4d723a4e2ad7d0695cd47cfa13 Mon Sep 17 00:00:00 2001 From: Duc Nguyen Date: Tue, 22 Sep 2020 10:13:55 -0400 Subject: [PATCH] Implement `Save without Formatting` command + Adds key binding of `ctrlcmd+k s` to save without formatting + Implements the command Signed-off-by: Duc Nguyen --- .../browser/common-frontend-contribution.ts | 15 +++++++++++++++ packages/core/src/browser/saveable.ts | 13 ++++++++++--- .../src/browser/shell/application-shell.ts | 8 ++++---- .../monaco/src/browser/monaco-editor-model.ts | 19 ++++++++++--------- .../src/browser/monaco-editor-provider.ts | 3 +++ 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts index e6aabd5389008..91b60b1f86ba8 100644 --- a/packages/core/src/browser/common-frontend-contribution.ts +++ b/packages/core/src/browser/common-frontend-contribution.ts @@ -230,6 +230,11 @@ export namespace CommonCommands { category: FILE_CATEGORY, label: 'Save', }; + export const SAVE_WITHOUT_FORMATTING: Command = { + id: 'core.saveWithoutFormatting', + category: FILE_CATEGORY, + label: 'Save without Formatting', + }; export const SAVE_ALL: Command = { id: 'core.saveAll', category: FILE_CATEGORY, @@ -445,6 +450,9 @@ export class CommonFrontendContribution implements FrontendApplicationContributi registry.registerMenuAction(CommonMenus.FILE_SAVE, { commandId: CommonCommands.SAVE.id }); + registry.registerMenuAction(CommonMenus.FILE_SAVE, { + commandId: CommonCommands.SAVE_WITHOUT_FORMATTING.id + }); registry.registerMenuAction(CommonMenus.FILE_SAVE, { commandId: CommonCommands.SAVE_ALL.id }); @@ -747,6 +755,9 @@ export class CommonFrontendContribution implements FrontendApplicationContributi commandRegistry.registerCommand(CommonCommands.SAVE, { execute: () => this.shell.save() }); + commandRegistry.registerCommand(CommonCommands.SAVE_WITHOUT_FORMATTING, { + execute: () => this.shell.save({ skipFormatting: true }) + }); commandRegistry.registerCommand(CommonCommands.SAVE_ALL, { execute: () => this.shell.saveAll() }); @@ -924,6 +935,10 @@ export class CommonFrontendContribution implements FrontendApplicationContributi command: CommonCommands.SAVE.id, keybinding: 'ctrlcmd+s' }, + { + command: CommonCommands.SAVE_WITHOUT_FORMATTING.id, + keybinding: 'ctrlcmd+k s' + }, { command: CommonCommands.SAVE_ALL.id, keybinding: 'ctrlcmd+alt+s' diff --git a/packages/core/src/browser/saveable.ts b/packages/core/src/browser/saveable.ts index e6e09221693b6..b430f216851b2 100644 --- a/packages/core/src/browser/saveable.ts +++ b/packages/core/src/browser/saveable.ts @@ -29,7 +29,7 @@ export interface Saveable { /** * Saves dirty changes. */ - save(): MaybePromise; + save(options?: SaveOptions): MaybePromise; /** * Reverts dirty changes. */ @@ -87,10 +87,10 @@ export namespace Saveable { return !!getDirty(arg); } // eslint-disable-next-line @typescript-eslint/no-explicit-any - export async function save(arg: any): Promise { + export async function save(arg: any, options?: SaveOptions): Promise { const saveable = get(arg); if (saveable) { - await saveable.save(); + await saveable.save(options); } } export function apply(widget: Widget): SaveableWidget | undefined { @@ -179,6 +179,13 @@ export namespace SaveableWidget { } } +export interface SaveOptions { + /** + * Controls whether formatting should be applied upon saving + */ + readonly skipFormatting?: boolean; +} + /** * The class name added to the dirty widget's title. */ diff --git a/packages/core/src/browser/shell/application-shell.ts b/packages/core/src/browser/shell/application-shell.ts index 6d34ab12f4b92..6b530118e0089 100644 --- a/packages/core/src/browser/shell/application-shell.ts +++ b/packages/core/src/browser/shell/application-shell.ts @@ -25,7 +25,7 @@ import { Message } from '@phosphor/messaging'; import { IDragEvent } from '@phosphor/dragdrop'; import { RecursivePartial, Event as CommonEvent, DisposableCollection, Disposable } from '../../common'; import { animationFrame } from '../browser'; -import { Saveable, SaveableWidget } from '../saveable'; +import { Saveable, SaveableWidget, SaveOptions } from '../saveable'; import { StatusBarImpl, StatusBarEntry, StatusBarAlignment } from '../status-bar/status-bar'; import { TheiaDockPanel, BOTTOM_AREA_ID, MAIN_AREA_ID } from './theia-dock-panel'; import { SidePanelHandler, SidePanel, SidePanelHandlerFactory } from './side-panel-handler'; @@ -1731,8 +1731,8 @@ export class ApplicationShell extends Widget { /** * Save the current widget if it is dirty. */ - async save(): Promise { - await Saveable.save(this.currentWidget); + async save(options?: SaveOptions): Promise { + await Saveable.save(this.currentWidget, options); } /** @@ -1746,7 +1746,7 @@ export class ApplicationShell extends Widget { * Save all dirty widgets. */ async saveAll(): Promise { - await Promise.all(this.tracker.widgets.map(Saveable.save)); + await Promise.all(this.tracker.widgets.map(widget => Saveable.save(widget))); } /** diff --git a/packages/monaco/src/browser/monaco-editor-model.ts b/packages/monaco/src/browser/monaco-editor-model.ts index f03fd4113995f..53f3031114701 100644 --- a/packages/monaco/src/browser/monaco-editor-model.ts +++ b/packages/monaco/src/browser/monaco-editor-model.ts @@ -22,7 +22,7 @@ import { Emitter, Event } from '@theia/core/lib/common/event'; import { CancellationTokenSource, CancellationToken } from '@theia/core/lib/common/cancellation'; import { Resource, ResourceError, ResourceVersion } from '@theia/core/lib/common/resource'; import { Range } from 'vscode-languageserver-types'; -import { Saveable } from '@theia/core/lib/browser/saveable'; +import { Saveable, SaveOptions } from '@theia/core/lib/browser/saveable'; import { MonacoToProtocolConverter } from './monaco-to-protocol-converter'; import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter'; import { ILogger, Loggable, Log } from '@theia/core/lib/common/logger'; @@ -36,6 +36,7 @@ type ITextEditorModel = monaco.editor.ITextEditorModel; export interface WillSaveMonacoModelEvent { readonly model: MonacoEditorModel; readonly reason: TextDocumentSaveReason; + readonly options?: SaveOptions; waitUntil(thenable: Thenable): void; } @@ -283,8 +284,8 @@ export class MonacoEditorModel implements ITextEditorModel, TextEditorDocument { return this; } - save(): Promise { - return this.scheduleSave(TextDocumentSaveReason.Manual); + save(options?: SaveOptions): Promise { + return this.scheduleSave(TextDocumentSaveReason.Manual, undefined, undefined, options); } protected pendingOperation = Promise.resolve(); @@ -397,8 +398,8 @@ export class MonacoEditorModel implements ITextEditorModel, TextEditorDocument { return this.saveCancellationTokenSource.token; } - protected scheduleSave(reason: TextDocumentSaveReason, token: CancellationToken = this.cancelSave(), overwriteEncoding?: boolean): Promise { - return this.run(() => this.doSave(reason, token, overwriteEncoding)); + protected scheduleSave(reason: TextDocumentSaveReason, token: CancellationToken = this.cancelSave(), overwriteEncoding?: boolean, options?: SaveOptions): Promise { + return this.run(() => this.doSave(reason, token, overwriteEncoding, options)); } protected ignoreContentChanges = false; @@ -457,12 +458,12 @@ export class MonacoEditorModel implements ITextEditorModel, TextEditorDocument { } } - protected async doSave(reason: TextDocumentSaveReason, token: CancellationToken, overwriteEncoding?: boolean): Promise { + protected async doSave(reason: TextDocumentSaveReason, token: CancellationToken, overwriteEncoding?: boolean, options?: SaveOptions): Promise { if (token.isCancellationRequested || !this.resource.saveContents) { return; } - await this.fireWillSaveModel(reason, token); + await this.fireWillSaveModel(reason, token, options); if (token.isCancellationRequested) { return; } @@ -496,7 +497,7 @@ export class MonacoEditorModel implements ITextEditorModel, TextEditorDocument { } } - protected async fireWillSaveModel(reason: TextDocumentSaveReason, token: CancellationToken): Promise { + protected async fireWillSaveModel(reason: TextDocumentSaveReason, token: CancellationToken, options?: SaveOptions): Promise { type EditContributor = Thenable; const firing = this.onWillSaveModelEmitter.sequence(async listener => { @@ -507,7 +508,7 @@ export class MonacoEditorModel implements ITextEditorModel, TextEditorDocument { const { version } = this; const event = { - model: this, reason, + model: this, reason, options, waitUntil: (thenable: EditContributor) => { if (Object.isFrozen(waitables)) { throw new Error('waitUntil cannot be called asynchronously.'); diff --git a/packages/monaco/src/browser/monaco-editor-provider.ts b/packages/monaco/src/browser/monaco-editor-provider.ts index 69392712a25a9..ac07669162610 100644 --- a/packages/monaco/src/browser/monaco-editor-provider.ts +++ b/packages/monaco/src/browser/monaco-editor-provider.ts @@ -293,6 +293,9 @@ export class MonacoEditorProvider { if (event.reason !== TextDocumentSaveReason.Manual) { return []; } + if (event.options?.skipFormatting) { + return []; + } const overrideIdentifier = editor.document.languageId; const uri = editor.uri.toString(); const formatOnSave = this.editorPreferences.get({ preferenceName: 'editor.formatOnSave', overrideIdentifier }, undefined, uri)!;