Skip to content

Commit

Permalink
Add editor.codeActionsOnSave
Browse files Browse the repository at this point in the history
Fixes microsoft#42092

Adds a way to run code actions on save using the `editor.codeActionsOnSave` setting. This setting lists code action kinds to be executed automatically when  the document is saved.
  • Loading branch information
mjbvz committed Apr 20, 2018
1 parent 07d85ac commit 66d0380
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 9 deletions.
8 changes: 8 additions & 0 deletions src/vs/editor/common/config/commonEditorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,14 @@ const editorConfiguration: IConfigurationNode = {
'default': EDITOR_DEFAULTS.contribInfo.lightbulbEnabled,
'description': nls.localize('codeActions', "Enables the code action lightbulb")
},
'editor.codeActionsOnSave': {
'type': 'array',
'items': {
'type': 'string'
},
'default': EDITOR_DEFAULTS.contribInfo.codeActionsOnSave,
'description': nls.localize('codeActionsOnSave', "List of code actions kinds to be run on save.")
},
'editor.selectionClipboard': {
'type': 'boolean',
'default': EDITOR_DEFAULTS.contribInfo.selectionClipboard,
Expand Down
22 changes: 19 additions & 3 deletions src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,10 @@ export interface IEditorOptions {
* Control the behavior and rendering of the code action lightbulb.
*/
lightbulb?: IEditorLightbulbOptions;
/**
* Code action kinds to be run on save.
*/
codeActionsOnSave?: string[];
/**
* Enable code folding
* Defaults to true.
Expand Down Expand Up @@ -850,6 +854,7 @@ export interface EditorContribOptions {
readonly find: InternalEditorFindOptions;
readonly colorDecorators: boolean;
readonly lightbulbEnabled: boolean;
readonly codeActionsOnSave: string[];
}

/**
Expand Down Expand Up @@ -1194,6 +1199,7 @@ export class InternalEditorOptions {
&& a.matchBrackets === b.matchBrackets
&& this._equalFindOptions(a.find, b.find)
&& a.colorDecorators === b.colorDecorators
&& arrays.equals(a.codeActionsOnSave, b.codeActionsOnSave)
&& a.lightbulbEnabled === b.lightbulbEnabled
);
}
Expand Down Expand Up @@ -1408,6 +1414,13 @@ function _stringSet<T>(value: T, defaultValue: T, allowedValues: T[]): T {
return value;
}

function _stringArray(value: any, defaultValue: string[]): string[] {
if (!Array.isArray(value)) {
return defaultValue;
}
return value.filter(x => typeof x === 'string');
}

function _clampedInt(value: any, defaultValue: number, minimum: number, maximum: number): number {
let r: number;
if (typeof value === 'undefined') {
Expand Down Expand Up @@ -1736,7 +1749,8 @@ export class EditorOptionsValidator {
matchBrackets: _boolean(opts.matchBrackets, defaults.matchBrackets),
find: find,
colorDecorators: _boolean(opts.colorDecorators, defaults.colorDecorators),
lightbulbEnabled: _boolean(opts.lightbulb ? opts.lightbulb.enabled : false, defaults.lightbulbEnabled)
lightbulbEnabled: _boolean(opts.lightbulb ? opts.lightbulb.enabled : false, defaults.lightbulbEnabled),
codeActionsOnSave: _stringArray(opts.codeActionsOnSave, [])
};
}
}
Expand Down Expand Up @@ -1839,7 +1853,8 @@ export class InternalEditorOptionsFactory {
matchBrackets: (accessibilityIsOn ? false : opts.contribInfo.matchBrackets), // DISABLED WHEN SCREEN READER IS ATTACHED
find: opts.contribInfo.find,
colorDecorators: opts.contribInfo.colorDecorators,
lightbulbEnabled: opts.contribInfo.lightbulbEnabled
lightbulbEnabled: opts.contribInfo.lightbulbEnabled,
codeActionsOnSave: opts.contribInfo.codeActionsOnSave
}
};
}
Expand Down Expand Up @@ -2305,6 +2320,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
globalFindClipboard: false
},
colorDecorators: true,
lightbulbEnabled: true
lightbulbEnabled: true,
codeActionsOnSave: []
},
};
21 changes: 15 additions & 6 deletions src/vs/editor/contrib/codeAction/codeActionCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,22 @@ export class QuickFixController implements IEditorContribution {
}

private async _onApplyCodeAction(action: CodeAction): TPromise<void> {
if (action.edit) {
await BulkEdit.perform(action.edit.edits, this._textModelService, this._fileService, this._editor);
}
await applyCodeAction(action, this._textModelService, this._fileService, this._commandService, this._editor);
}
}

if (action.command) {
await this._commandService.executeCommand(action.command.id, ...action.command.arguments);
}
export async function applyCodeAction(
action: CodeAction,
textModelService: ITextModelService,
fileService: IFileService,
commandService: ICommandService,
editor: ICodeEditor,
) {
if (action.edit) {
await BulkEdit.perform(action.edit.edits, textModelService, fileService, editor);
}
if (action.command) {
await commandService.executeCommand(action.command.id, ...action.command.arguments);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2823,6 +2823,10 @@ declare namespace monaco.editor {
* Control the behavior and rendering of the code action lightbulb.
*/
lightbulb?: IEditorLightbulbOptions;
/**
* Code action kinds to be run on save.
*/
codeActionsOnSave?: string[];
/**
* Enable code folding
* Defaults to true.
Expand Down Expand Up @@ -3118,6 +3122,7 @@ declare namespace monaco.editor {
readonly find: InternalEditorFindOptions;
readonly colorDecorators: boolean;
readonly lightbulbEnabled: boolean;
readonly codeActionsOnSave: string[];
}

/**
Expand Down
47 changes: 47 additions & 0 deletions src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IFileService } from 'vs/platform/files/common/files';

export interface ISaveParticipantParticipant extends ISaveParticipant {
// progressMessage: string;
Expand Down Expand Up @@ -259,6 +262,49 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant {
}
}

class CodeActionOnParticipant implements ISaveParticipant {

constructor(
@ITextModelService private readonly _textModelService: ITextModelService,
@IFileService private readonly _fileService: IFileService,
@ICommandService private readonly _commandService: ICommandService,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) { }

async participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
if (env.reason === SaveReason.AUTO) {
return undefined;
}

const model = editorModel.textEditorModel;
const editor = findEditor(model, this._codeEditorService);
if (!editor) {
return undefined;
}

const codeActionsOnSave = this._configurationService.getValue<string[]>('editor.codeActionsOnSave', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }).map(x => new CodeActionKind(x));
if (!codeActionsOnSave.length) {
return undefined;
}

const actionsToRun = await this.getActionsToRun(model, codeActionsOnSave);
await this.applyCodeActions(actionsToRun, editor);
}

private async applyCodeActions(actionsToRun: CodeAction[], editor: ICodeEditor) {
for (const action of actionsToRun) {
await applyCodeAction(action, this._textModelService, this._fileService, this._commandService, editor);
}
}

private async getActionsToRun(model: ITextModel, codeActionsOnSave: CodeActionKind[]) {
const actions = await getCodeActions(model, model.getFullModelRange(), { kind: CodeActionKind.Source, includeSourceActions: true });
const actionsToRun = actions.filter(returnedAction => returnedAction.kind && codeActionsOnSave.some(onSaveKind => onSaveKind.contains(returnedAction.kind)));
return actionsToRun;
}
}

class ExtHostSaveParticipant implements ISaveParticipantParticipant {

private _proxy: ExtHostDocumentSaveParticipantShape;
Expand Down Expand Up @@ -303,6 +349,7 @@ export class SaveParticipant implements ISaveParticipant {
) {
this._saveParticipants = [
instantiationService.createInstance(TrimWhitespaceParticipant),
instantiationService.createInstance(CodeActionOnParticipant),
instantiationService.createInstance(FormatOnSaveParticipant),
instantiationService.createInstance(FinalNewLineParticipant),
instantiationService.createInstance(TrimFinalNewLinesParticipant),
Expand Down

0 comments on commit 66d0380

Please sign in to comment.