Skip to content

Commit

Permalink
Address PR comments
Browse files Browse the repository at this point in the history
  • Loading branch information
c-claeys committed Feb 16, 2023
1 parent 17f5d8b commit 636dd22
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 131 deletions.
8 changes: 6 additions & 2 deletions src/vs/editor/common/languages.ts
Expand Up @@ -1182,6 +1182,10 @@ export interface FormattingOptions {
* Prefer spaces over tabs.
*/
insertSpaces: boolean;
/**
* The list of multiple ranges to format at once, if the provider supports it.
*/
ranges?: Range[];
}
/**
* The document formatting provider interface defines the contract between extensions and
Expand Down Expand Up @@ -1213,7 +1217,7 @@ export interface DocumentRangeFormattingEditProvider {

readonly displayName?: string;

readonly multiRange: boolean;
readonly canFormatMultipleRanges: boolean;

/**
* Provide formatting edits for a range in a document.
Expand All @@ -1222,7 +1226,7 @@ export interface DocumentRangeFormattingEditProvider {
* or larger range. Often this is done by adjusting the start and end
* of the range to full syntax nodes.
*/
provideDocumentRangeFormattingEdits(model: model.ITextModel, range: Range | Range[], options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]>;
provideDocumentRangeFormattingEdits(model: model.ITextModel, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]>;
}
/**
* The document formatting provider interface defines the contract between extensions and
Expand Down
150 changes: 50 additions & 100 deletions src/vs/editor/contrib/format/browser/format.ts
Expand Up @@ -107,11 +107,6 @@ export interface IFormattingEditProviderSelector {
<T extends (DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider)>(formatter: T[], document: ITextModel, mode: FormattingMode): Promise<T | undefined>;
}

interface IEditResult {
cancelled: boolean;
allEdits: TextEdit[];
}

export abstract class FormattingConflicts {

private static readonly _selectors = new LinkedList<IFormattingEditProviderSelector>();
Expand Down Expand Up @@ -172,95 +167,7 @@ export async function formatDocumentRangesWithProvider(
model = editorOrModel;
cts = new TextModelCancellationTokenSource(editorOrModel, token);
}
let result: IEditResult;
if (provider.multiRange) {
result = await formatDocumentRangesWithMultiRangeProvider(provider, editorOrModel, rangeOrRanges, cts, model, workerService);
} else {
result = await formatDocumentRangesWithSingleRangeProvider(provider, editorOrModel, rangeOrRanges, cts, model, workerService);
}
if (result.cancelled) { return true; }

const { allEdits } = result;

if (allEdits.length === 0) {
return false;
}

if (isCodeEditor(editorOrModel)) {
// use editor to apply edits
FormattingEdit.execute(editorOrModel, allEdits, true);
alertFormattingEdits(allEdits);
editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), ScrollType.Immediate);

} else {
// use model to apply edits
const [{ range }] = allEdits;
const initialSelection = new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
model.pushEditOperations([initialSelection], allEdits.map(edit => {
return {
text: edit.text,
range: Range.lift(edit.range),
forceMoveMarkers: true
};
}), undoEdits => {
for (const { range } of undoEdits) {
if (Range.areIntersectingOrTouching(range, initialSelection)) {
return [new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)];
}
}
return null;
});
}

return true;
}

async function formatDocumentRangesWithMultiRangeProvider(
provider: DocumentRangeFormattingEditProvider,
editorOrModel: ITextModel | IActiveCodeEditor,
rangeOrRanges: Range | Range[],
cts: CancellationTokenSource,
model: ITextModel,
workerService: IEditorWorkerService
): Promise<IEditResult> {
const ranges = asArray(rangeOrRanges);

const allEdits: TextEdit[] = [];
try {
if (cts.token.isCancellationRequested) {
return { cancelled: true, allEdits };
}

const rawEdits = (await provider.provideDocumentRangeFormattingEdits(
model,
ranges,
model.getFormattingOptions(),
cts.token
)) || [];

if (cts.token.isCancellationRequested) {
return { cancelled: true, allEdits };
}

const minimalEdits = await workerService.computeMoreMinimalEdits(model.uri, rawEdits);
if (minimalEdits) {
allEdits.push(...minimalEdits);
}
} finally {
cts.dispose();
}

return { cancelled: false, allEdits };
}

async function formatDocumentRangesWithSingleRangeProvider(
provider: DocumentRangeFormattingEditProvider,
editorOrModel: ITextModel | IActiveCodeEditor,
rangeOrRanges: Range | Range[],
cts: CancellationTokenSource,
model: ITextModel,
workerService: IEditorWorkerService
): Promise<IEditResult> {
// make sure that ranges don't overlap nor touch each other
const ranges: Range[] = [];
let len = 0;
Expand Down Expand Up @@ -310,17 +217,29 @@ async function formatDocumentRangesWithSingleRangeProvider(
const allEdits: TextEdit[] = [];
const rawEditsList: TextEdit[][] = [];
try {
for (const range of ranges) {
if (cts.token.isCancellationRequested) {
return { cancelled: true, allEdits };
if (provider.canFormatMultipleRanges) {
logService.trace(`[format][provideDocumentRangeFormattingEdits] (request)`, provider.extensionId?.value, ranges);
const result = (await provider.provideDocumentRangeFormattingEdits(
model,
ranges[0],
{ ...model.getFormattingOptions(), ranges },
cts.token
)) || [];
logService.trace(`[format][provideDocumentRangeFormattingEdits] (response)`, provider.extensionId?.value, result);
rawEditsList.push(result);
} else {
for (const range of ranges) {
if (cts.token.isCancellationRequested) {
return true;
}
rawEditsList.push(await computeEdits(range));
}
rawEditsList.push(await computeEdits(range));
}

for (let i = 0; i < ranges.length; ++i) {
for (let j = i + 1; j < ranges.length; ++j) {
if (cts.token.isCancellationRequested) {
return { cancelled: true, allEdits };
return true;
}
if (hasIntersectingEdit(rawEditsList[i], rawEditsList[j])) {
// Merge ranges i and j into a single range, recompute the associated edits
Expand All @@ -341,7 +260,7 @@ async function formatDocumentRangesWithSingleRangeProvider(

for (const rawEdits of rawEditsList) {
if (cts.token.isCancellationRequested) {
return { cancelled: true, allEdits };
return true;
}
const minimalEdits = await workerService.computeMoreMinimalEdits(model.uri, rawEdits);
if (minimalEdits) {
Expand All @@ -351,7 +270,38 @@ async function formatDocumentRangesWithSingleRangeProvider(
} finally {
cts.dispose();
}
return { cancelled: false, allEdits };

if (allEdits.length === 0) {
return false;
}

if (isCodeEditor(editorOrModel)) {
// use editor to apply edits
FormattingEdit.execute(editorOrModel, allEdits, true);
alertFormattingEdits(allEdits);
editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), ScrollType.Immediate);

} else {
// use model to apply edits
const [{ range }] = allEdits;
const initialSelection = new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
model.pushEditOperations([initialSelection], allEdits.map(edit => {
return {
text: edit.text,
range: Range.lift(edit.range),
forceMoveMarkers: true
};
}), undoEdits => {
for (const { range } of undoEdits) {
if (Range.areIntersectingOrTouching(range, initialSelection)) {
return [new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)];
}
}
return null;
});
}

return true;
}

export async function formatDocumentWithSelectedProvider(
Expand Down
8 changes: 6 additions & 2 deletions src/vs/monaco.d.ts
Expand Up @@ -7121,6 +7121,10 @@ declare namespace monaco.languages {
* Prefer spaces over tabs.
*/
insertSpaces: boolean;
/**
* The list of multiple ranges to format at once, if the provider supports it.
*/
ranges?: Range[];
}

/**
Expand All @@ -7141,15 +7145,15 @@ declare namespace monaco.languages {
*/
export interface DocumentRangeFormattingEditProvider {
readonly displayName?: string;
readonly multiRange: boolean;
readonly canFormatMultipleRanges: boolean;
/**
* Provide formatting edits for a range in a document.
*
* The given range is a hint and providers can decide to format a smaller
* or larger range. Often this is done by adjusting the start and end
* of the range to full syntax nodes.
*/
provideDocumentRangeFormattingEdits(model: editor.ITextModel, range: Range | Range[], options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]>;
provideDocumentRangeFormattingEdits(model: editor.ITextModel, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]>;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts
Expand Up @@ -391,7 +391,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
this._registrations.set(handle, this._languageFeaturesService.documentRangeFormattingEditProvider.register(selector, <languages.DocumentRangeFormattingEditProvider>{
extensionId,
displayName,
multiRange: metadata.multiRange,
canFormatMultipleRanges: metadata.canFormatMultipleRanges,
provideDocumentRangeFormattingEdits: (model: ITextModel, range: EditorRange | EditorRange[], options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined> => {
return this._proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options, token);
}
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHost.api.impl.ts
Expand Up @@ -526,7 +526,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerDocumentFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable {
return extHostLanguageFeatures.registerDocumentFormattingEditProvider(extension, checkSelector(selector), provider);
},
registerDocumentRangeFormattingEditProvider<T extends vscode.Range | vscode.Range[]>(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider<T>, metadata?: vscode.DocumentRangeFormattingEditProviderMetadata): vscode.Disposable {
registerDocumentRangeFormattingEditProvider<T extends vscode.Range | vscode.Range[]>(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider, metadata?: vscode.DocumentRangeFormattingEditProviderMetadata): vscode.Disposable {
return extHostLanguageFeatures.registerDocumentRangeFormattingEditProvider<T>(extension, checkSelector(selector), provider, metadata);
},
registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]): vscode.Disposable {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Expand Up @@ -353,7 +353,7 @@ export interface IDocumentFilterDto {
}

export interface IRangeFormattingProviderMetadataDto {
readonly multiRange?: boolean;
readonly canFormatMultipleRanges?: boolean;
}

export interface ISignatureHelpProviderMetadataDto {
Expand Down
39 changes: 20 additions & 19 deletions src/vs/workbench/api/common/extHostLanguageFeatures.ts
Expand Up @@ -547,27 +547,28 @@ class DocumentFormattingAdapter {
}
}

class RangeFormattingAdapter<T extends vscode.Range | vscode.Range[]> {
class RangeFormattingAdapter {

constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.DocumentRangeFormattingEditProvider<T>,
private readonly _multiRange: boolean
private readonly _provider: vscode.DocumentRangeFormattingEditProvider,
private readonly _canFormatMultipleRanges: boolean
) { }

async provideDocumentRangeFormattingEdits(resource: URI, range: IRange | IRange[], options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined> {
async provideDocumentRangeFormattingEdits(resource: URI, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined> {

const document = this._documents.getDocument(resource);
let ran: Range | Range[];
if (this._multiRange) {
if (!Array.isArray(range)) { range = [range]; }
ran = range.map<Range>(typeConvert.Range.to);
if (this._canFormatMultipleRanges) {
if (!Array.isArray(options.ranges)) { throw new Error('Provided no list of ranges to multiple range provider'); }

options.ranges = options.ranges.map<Range>(typeConvert.Range.to) as any;
} else {
if (Array.isArray(range)) { throw new Error('Provided list of ranges to non-multiRange provider'); }
ran = typeConvert.Range.to(range);
if (Array.isArray(range)) { throw new Error('Provided list of ranges to single range provider'); }
}

const value = await this._provider.provideDocumentRangeFormattingEdits(document, <any>ran, <any>options, token);
const document = this._documents.getDocument(resource);
const ran = typeConvert.Range.to(range);

const value = await this._provider.provideDocumentRangeFormattingEdits(document, ran, <any>options, token);
if (Array.isArray(value)) {
return value.map(typeConvert.TextEdit.from);
}
Expand Down Expand Up @@ -1730,9 +1731,9 @@ class DocumentOnDropEditAdapter {
}
}

type Adapter<T extends vscode.Range | vscode.Range[] = vscode.Range | vscode.Range[]> = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter
type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter
| DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentPasteEditProvider | DocumentFormattingAdapter
| RangeFormattingAdapter<T> | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
| CompletionsAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
| SelectionRangeAdapter | CallHierarchyAdapter | TypeHierarchyAdapter
Expand Down Expand Up @@ -1823,7 +1824,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
return result;
}

private _addNewAdapter<T extends vscode.Range | vscode.Range[]>(adapter: Adapter<T>, extension: IExtensionDescription): number {
private _addNewAdapter(adapter: Adapter, extension: IExtensionDescription): number {
const handle = this._nextHandle();
this._adapter.set(handle, new AdapterData(adapter, extension));
return handle;
Expand Down Expand Up @@ -2049,13 +2050,13 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
return this._withAdapter(handle, DocumentFormattingAdapter, adapter => adapter.provideDocumentFormattingEdits(URI.revive(resource), options, token), undefined, token);
}

registerDocumentRangeFormattingEditProvider<T extends vscode.Range | vscode.Range[]>(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider<T>, metadata?: vscode.DocumentRangeFormattingEditProviderMetadata): vscode.Disposable {
const handle = this._addNewAdapter<T>(new RangeFormattingAdapter<T>(this._documents, provider, metadata?.multiRange ?? false), extension);
this._proxy.$registerRangeFormattingSupport(handle, this._transformDocumentSelector(selector), extension.identifier, extension.displayName || extension.name, metadata ?? { multiRange: false });
registerDocumentRangeFormattingEditProvider<T extends vscode.Range | vscode.Range[]>(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider, metadata?: vscode.DocumentRangeFormattingEditProviderMetadata): vscode.Disposable {
const handle = this._addNewAdapter(new RangeFormattingAdapter(this._documents, provider, metadata?.canFormatMultipleRanges ?? false), extension);
this._proxy.$registerRangeFormattingSupport(handle, this._transformDocumentSelector(selector), extension.identifier, extension.displayName || extension.name, metadata ?? { canFormatMultipleRanges: false });
return this._createDisposable(handle);
}

$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange | IRange[], options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined> {
$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined> {
return this._withAdapter(handle, RangeFormattingAdapter, adapter => adapter.provideDocumentRangeFormattingEdits(URI.revive(resource), range, options, token), undefined, token);
}

Expand Down

0 comments on commit 636dd22

Please sign in to comment.