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

format - extract formatting logic, make it so that we potentially run multiple...

format - extract formatting logic, make it so that we potentially run multiple providers sequentially (off for now)
上级 45d4e218
...@@ -17,11 +17,15 @@ import { CancellationToken } from 'vs/base/common/cancellation'; ...@@ -17,11 +17,15 @@ import { CancellationToken } from 'vs/base/common/cancellation';
export class NoProviderError extends Error { export class NoProviderError extends Error {
static readonly Name = 'NOPRO'; static is(thing: any): thing is NoProviderError {
return thing instanceof Error && thing.name === NoProviderError._name;
}
private static readonly _name = 'NOPRO';
constructor(message?: string) { constructor(message?: string) {
super(); super();
this.name = NoProviderError.Name; this.name = NoProviderError._name;
if (message) { if (message) {
this.message = message; this.message = message;
} }
......
...@@ -9,9 +9,9 @@ import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; ...@@ -9,9 +9,9 @@ import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as editorCommon from 'vs/editor/common/editorCommon'; import * as editorCommon from 'vs/editor/common/editorCommon';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution, IActionOptions } from 'vs/editor/browser/editorExtensions'; import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { OnTypeFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry } from 'vs/editor/common/modes'; import { OnTypeFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry, DocumentFormattingEditProviderRegistry, FormattingOptions } from 'vs/editor/common/modes';
import { getOnTypeFormattingEdits, getDocumentFormattingEdits, getDocumentRangeFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format'; import { getOnTypeFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format';
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit'; import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
...@@ -21,12 +21,12 @@ import { Range } from 'vs/editor/common/core/range'; ...@@ -21,12 +21,12 @@ import { Range } from 'vs/editor/common/core/range';
import { alert } from 'vs/base/browser/ui/aria/aria'; import { alert } from 'vs/base/browser/ui/aria/aria';
import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ISingleEditOperation } from 'vs/editor/common/model'; import { ISingleEditOperation } from 'vs/editor/common/model';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { sequence } from 'vs/base/common/async';
function alertFormattingEdits(edits: ISingleEditOperation[]): void { function alertFormattingEdits(edits: ISingleEditOperation[]): void {
...@@ -55,6 +55,98 @@ function alertFormattingEdits(edits: ISingleEditOperation[]): void { ...@@ -55,6 +55,98 @@ function alertFormattingEdits(edits: ISingleEditOperation[]): void {
} }
} }
export const enum FormatRangeType {
Full,
Selection,
}
export function formatDocumentRange(workerService: IEditorWorkerService, editor: IActiveCodeEditor, rangeOrRangeType: Range | FormatRangeType, options: FormattingOptions, token: CancellationToken): Promise<void> {
const provider = DocumentRangeFormattingEditProviderRegistry.ordered(editor.getModel()).slice(0, 1);
if (provider.length === 0) {
return Promise.reject(new NoProviderError());
}
let allEdits: ISingleEditOperation[] = [];
editor.pushUndoStop();
return sequence(provider.map(provider => {
// create a formatting task per provider. they run sequentially,
// potentially undoing the working of a previous formatter
return () => {
const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
const model = editor.getModel();
let range: Range;
if (rangeOrRangeType === FormatRangeType.Full) {
// full
range = model.getFullModelRange();
} else if (rangeOrRangeType === FormatRangeType.Selection) {
// selection or line (when empty)
range = editor.getSelection();
if (range.isEmpty()) {
range = new Range(range.startLineNumber, 1, range.endLineNumber, model.getLineMaxColumn(range.endLineNumber));
}
} else {
// as is
range = rangeOrRangeType;
}
return Promise.resolve(provider.provideDocumentRangeFormattingEdits(model, range, options, token)).then(edits => {
// break edits into smaller edits
return workerService.computeMoreMinimalEdits(editor.getModel().uri, edits);
}).then(edits => {
// make edit only when the editor didn't change while
// computing and only when there are edits
if (state.validate(editor) && isNonEmptyArray(edits)) {
FormattingEdit.execute(editor, edits);
allEdits = allEdits.concat(edits);
}
});
};
})).then(() => {
alertFormattingEdits(allEdits);
editor.pushUndoStop();
editor.focus();
editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate);
});
}
export function formatDocument(workerService: IEditorWorkerService, editor: IActiveCodeEditor, options: FormattingOptions, token: CancellationToken): Promise<void> {
const provider = DocumentFormattingEditProviderRegistry.ordered(editor.getModel()).slice(0);
if (provider.length === 0) {
return formatDocumentRange(workerService, editor, FormatRangeType.Full, options, token);
}
let allEdits: ISingleEditOperation[] = [];
editor.pushUndoStop();
return sequence(provider.map(provider => {
// create a formatting task per provider. they run sequentially,
// potentially undoing the working of a previous formatter
return () => {
const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
const model = editor.getModel();
return Promise.resolve(provider.provideDocumentFormattingEdits(model, options, token)).then(edits => {
// break edits into smaller edits
return workerService.computeMoreMinimalEdits(editor.getModel().uri, edits);
}).then(edits => {
// make edit only when the editor didn't change while
// computing and only when there are edits
if (state.validate(editor) && isNonEmptyArray(edits)) {
FormattingEdit.execute(editor, edits);
allEdits = allEdits.concat(edits);
}
});
};
})).then(() => {
alertFormattingEdits(allEdits);
editor.pushUndoStop();
editor.focus();
editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate);
});
}
class FormatOnType implements editorCommon.IEditorContribution { class FormatOnType implements editorCommon.IEditorContribution {
private static readonly ID = 'editor.contrib.autoFormat'; private static readonly ID = 'editor.contrib.autoFormat';
...@@ -224,8 +316,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution { ...@@ -224,8 +316,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
let model = this.editor.getModel(); let model = this.editor.getModel();
// no support // no support
let [support] = DocumentRangeFormattingEditProviderRegistry.ordered(model); if (!DocumentRangeFormattingEditProviderRegistry.has(model)) {
if (!support || !support.provideDocumentRangeFormattingEdits) {
return; return;
} }
...@@ -245,19 +336,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution { ...@@ -245,19 +336,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
const model = this.editor.getModel(); const model = this.editor.getModel();
const { tabSize, insertSpaces } = model.getOptions(); const { tabSize, insertSpaces } = model.getOptions();
const state = new EditorState(this.editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); formatDocumentRange(this.workerService, this.editor, range, { tabSize, insertSpaces }, CancellationToken.None);
getDocumentRangeFormattingEdits(model, range, { tabSize, insertSpaces }, CancellationToken.None).then(edits => {
return this.workerService.computeMoreMinimalEdits(model.uri, edits);
}).then(edits => {
if (!state.validate(this.editor)) {
return;
}
if (isNonEmptyArray(edits)) {
FormattingEdit.execute(this.editor, edits);
alertFormattingEdits(edits);
}
});
} }
public getId(): string { public getId(): string {
...@@ -270,53 +349,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution { ...@@ -270,53 +349,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
} }
} }
export abstract class AbstractFormatAction extends EditorAction { export class FormatDocumentAction extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
if (!editor.hasModel()) {
return Promise.resolve(void 0);
}
const workerService = accessor.get(IEditorWorkerService);
const notificationService = accessor.get(INotificationService);
const formattingPromise = this._getFormattingEdits(editor, CancellationToken.None);
if (!formattingPromise) {
return Promise.resolve(void 0);
}
// Capture the state of the editor
const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
// Receive formatted value from worker
return formattingPromise.then(edits => workerService.computeMoreMinimalEdits(editor.getModel().uri, edits)).then(edits => {
if (!state.validate(editor)) {
return;
}
if (isNonEmptyArray(edits)) {
FormattingEdit.execute(editor, edits);
alertFormattingEdits(edits);
editor.focus();
editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate);
}
}, err => {
if (err instanceof Error && err.name === NoProviderError.Name) {
this._notifyNoProviderError(notificationService, editor.getModel().getLanguageIdentifier().language);
} else {
throw err;
}
});
}
protected abstract _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[] | null | undefined> | undefined;
protected _notifyNoProviderError(notificationService: INotificationService, language: string): void {
notificationService.info(nls.localize('no.provider', "There is no formatter for '{0}'-files installed.", language));
}
}
export class FormatDocumentAction extends AbstractFormatAction {
constructor() { constructor() {
super({ super({
...@@ -339,21 +372,22 @@ export class FormatDocumentAction extends AbstractFormatAction { ...@@ -339,21 +372,22 @@ export class FormatDocumentAction extends AbstractFormatAction {
}); });
} }
protected _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[] | null | undefined> | undefined { run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | void {
if (!editor.hasModel()) { if (!editor.hasModel()) {
return undefined; return;
} }
const model = editor.getModel(); const notificationService = accessor.get(INotificationService);
const { tabSize, insertSpaces } = model.getOptions(); const workerService = accessor.get(IEditorWorkerService);
return getDocumentFormattingEdits(model, { tabSize, insertSpaces }, token); const { tabSize, insertSpaces } = editor.getModel().getOptions();
} return formatDocument(workerService, editor, { tabSize, insertSpaces }, CancellationToken.None).catch(err => {
if (NoProviderError.is(err)) {
protected _notifyNoProviderError(notificationService: INotificationService, language: string): void { notificationService.info(nls.localize('no.documentprovider', "There is no document formatter for '{0}'-files installed.", editor.getModel().getLanguageIdentifier().language));
notificationService.info(nls.localize('no.documentprovider', "There is no document formatter for '{0}'-files installed.", language)); }
});
} }
} }
export class FormatSelectionAction extends AbstractFormatAction { export class FormatSelectionAction extends EditorAction {
constructor() { constructor() {
super({ super({
...@@ -374,24 +408,18 @@ export class FormatSelectionAction extends AbstractFormatAction { ...@@ -374,24 +408,18 @@ export class FormatSelectionAction extends AbstractFormatAction {
}); });
} }
protected _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[] | null | undefined> | undefined { run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | void {
if (!editor.hasModel()) { if (!editor.hasModel()) {
return undefined; return;
}
const model = editor.getModel();
let selection = editor.getSelection();
if (selection.isEmpty()) {
const maxColumn = model.getLineMaxColumn(selection.startLineNumber);
selection = selection.setStartPosition(selection.startLineNumber, 1);
selection = selection.setEndPosition(selection.endLineNumber, maxColumn);
} }
const { tabSize, insertSpaces } = model.getOptions(); const notificationService = accessor.get(INotificationService);
return getDocumentRangeFormattingEdits(model, selection, { tabSize, insertSpaces }, token); const workerService = accessor.get(IEditorWorkerService);
} const { tabSize, insertSpaces } = editor.getModel().getOptions();
return formatDocumentRange(workerService, editor, FormatRangeType.Selection, { tabSize, insertSpaces }, CancellationToken.None).catch(err => {
protected _notifyNoProviderError(notificationService: INotificationService, language: string): void { if (NoProviderError.is(err)) {
notificationService.info(nls.localize('no.selectionprovider', "There is no selection formatter for '{0}'-files installed.", language)); notificationService.info(nls.localize('no.selectionprovider', "There is no selection formatter for '{0}'-files installed.", editor.getModel().getLanguageIdentifier().language));
}
});
} }
} }
...@@ -404,25 +432,15 @@ registerEditorAction(FormatSelectionAction); ...@@ -404,25 +432,15 @@ registerEditorAction(FormatSelectionAction);
// and we keep it here such that existing keybinding configurations etc will still work // and we keep it here such that existing keybinding configurations etc will still work
CommandsRegistry.registerCommand('editor.action.format', accessor => { CommandsRegistry.registerCommand('editor.action.format', accessor => {
const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
if (editor) { if (!editor || !editor.hasModel()) {
return new class extends AbstractFormatAction { return undefined;
constructor() { }
super({} as IActionOptions); const { tabSize, insertSpaces } = editor.getModel().getOptions();
} const workerService = accessor.get(IEditorWorkerService);
_getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[] | null | undefined> | undefined {
if (!editor.hasModel()) {
return undefined;
}
const model = editor.getModel();
const editorSelection = editor.getSelection();
const { tabSize, insertSpaces } = model.getOptions();
return editorSelection.isEmpty() if (editor.getSelection().isEmpty()) {
? getDocumentFormattingEdits(model, { tabSize, insertSpaces }, token) return formatDocument(workerService, editor, { tabSize, insertSpaces }, CancellationToken.None);
: getDocumentRangeFormattingEdits(model, editorSelection, { tabSize, insertSpaces }, token); } else {
} return formatDocumentRange(workerService, editor, FormatRangeType.Selection, { tabSize, insertSpaces }, CancellationToken.None);
}().run(accessor, editor);
} }
return undefined;
}); });
...@@ -243,7 +243,7 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { ...@@ -243,7 +243,7 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant {
}, timeout); }, timeout);
request.then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits)).then(resolve, err => { request.then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits)).then(resolve, err => {
if (!(err instanceof Error) || err.name !== NoProviderError.Name) { if (!NoProviderError.is(err)) {
reject(err); reject(err);
} else { } else {
resolve(); resolve();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册