Skip to content

Commit

Permalink
VSCode extension: add format selection support
Browse files Browse the repository at this point in the history
  • Loading branch information
Richard Willis committed Nov 17, 2024
1 parent 6b7d239 commit 89e077f
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 80 deletions.
19 changes: 4 additions & 15 deletions Src/CSharpier.VSCode/src/DiagnosticsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Difference, generateDifferences, showInvisibles } from "prettier-linter
import { FixAllCodeActionsCommand } from "./FixAllCodeActionCommand";
import { CSharpierProcessProvider } from "./CSharpierProcessProvider";
import { Logger } from "./Logger";
import { FormatDocumentProvider } from "./FormatDocumentProvider";

const DIAGNOSTICS_ID = "csharpier";
const DIAGNOSTICS_SOURCE_ID = "diagnostic";
Expand All @@ -26,7 +27,7 @@ export class DiagnosticsService implements vscode.CodeActionProvider, vscode.Dis
private readonly disposables: vscode.Disposable[] = [];

constructor(
private readonly csharpierProcessProvider: CSharpierProcessProvider,
private readonly formatDocumentProvider: FormatDocumentProvider,
private readonly documentSelector: Array<vscode.DocumentFilter>,
private readonly logger: Logger,
) {
Expand Down Expand Up @@ -154,20 +155,8 @@ export class DiagnosticsService implements vscode.CodeActionProvider, vscode.Dis

private async getDiff(document: vscode.TextDocument): Promise<CsharpierDiff> {
const source = document.getText();
const csharpierProcess = this.csharpierProcessProvider.getProcessFor(document.fileName);
let formattedSource = "";
if ("formatFile2" in csharpierProcess) {
const parameter = {
fileContents: source,
fileName: document.fileName,
};
const result = await csharpierProcess.formatFile2(parameter);
if (result) {
formattedSource = result.formattedFile;
}
} else {
formattedSource = await csharpierProcess.formatFile(source, document.fileName);
}
const formattedSource =
(await this.formatDocumentProvider.formatDocument(document)) ?? source;
const differences = generateDifferences(source, formattedSource);
return {
source,
Expand Down
6 changes: 4 additions & 2 deletions Src/CSharpier.VSCode/src/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NullCSharpierProcess } from "./NullCSharpierProcess";
import { FixAllCodeActionsCommand } from "./FixAllCodeActionCommand";
import { DiagnosticsService } from "./DiagnosticsService";
import { FixAllCodeActionProvider } from "./FixAllCodeActionProvider";
import { FormatDocumentProvider } from "./FormatDocumentProvider";

export async function activate(context: ExtensionContext) {
if (!workspace.isTrusted) {
Expand Down Expand Up @@ -40,20 +41,21 @@ const initPlugin = async (context: ExtensionContext) => {
NullCSharpierProcess.create(logger);

const csharpierProcessProvider = new CSharpierProcessProvider(logger, context.extension);
const formatDocumentProvider = new FormatDocumentProvider(logger, csharpierProcessProvider);
const diagnosticsDocumentSelector: DocumentFilter[] = [
{
language: "csharp",
scheme: "file",
},
];
const diagnosticsService = new DiagnosticsService(
csharpierProcessProvider,
formatDocumentProvider,
diagnosticsDocumentSelector,
logger,
);
const fixAllCodeActionProvider = new FixAllCodeActionProvider(diagnosticsDocumentSelector);

new FormattingService(logger, csharpierProcessProvider);
new FormattingService(formatDocumentProvider);
new FixAllCodeActionsCommand(context, csharpierProcessProvider, logger);

context.subscriptions.push(
Expand Down
64 changes: 64 additions & 0 deletions Src/CSharpier.VSCode/src/FormatDocumentProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { TextDocument } from "vscode";
import { Logger } from "./Logger";
import { CSharpierProcessProvider } from "./CSharpierProcessProvider";
import { performance } from "perf_hooks";

export class FormatDocumentProvider {
constructor(
private logger: Logger,
private csharpierProcessProvider: CSharpierProcessProvider,
) {}

async formatDocument(document: TextDocument): Promise<string | null> {
const csharpierProcess = this.csharpierProcessProvider.getProcessFor(document.fileName);
const text = document.getText();
const startTime = performance.now();

if ("formatFile2" in csharpierProcess) {
const parameter = {
fileContents: text,
fileName: document.fileName,
};
const result = await csharpierProcess.formatFile2(parameter);

this.logger.info("Formatted in " + (performance.now() - startTime) + "ms");

if (result == null) {
return null;
}

switch (result.status) {
case "Formatted":
return result.formattedFile;
case "Ignored":
this.logger.info("File is ignored by csharpier cli.");
break;
case "Failed":
this.logger.warn(
"CSharpier cli failed to format the file and returned the following error: " +
result.errorMessage,
);
break;
default:
this.logger.warn("Didn't handle " + result.status);
break;
}
} else {
const newText = await csharpierProcess.formatFile(text, document.fileName);
const endTime = performance.now();
this.logger.info("Formatted in " + (endTime - startTime) + "ms");
if (!newText || newText === text) {
this.logger.debug(
"Skipping write because " + !newText
? "result is empty"
: "current document equals result",
);
return null;
}

return newText;
}

return null;
}
}
133 changes: 70 additions & 63 deletions Src/CSharpier.VSCode/src/FormattingService.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,88 @@
import { performance } from "perf_hooks";
import { languages, Range, TextDocument, TextEdit } from "vscode";
import { CSharpierProcessProvider } from "./CSharpierProcessProvider";
import { Logger } from "./Logger";
import { Status } from "./ICSharpierProcess";
import {
CancellationToken,
Diagnostic,
FormattingOptions,
languages,
Position,
Range,
TextDocument,
TextEdit,
WorkspaceEdit,
} from "vscode";
import { Difference, generateDifferences } from "prettier-linter-helpers";
import { FormatDocumentProvider } from "./FormatDocumentProvider";

export class FormattingService {
logger: Logger;
csharpierProcessProvider: CSharpierProcessProvider;

constructor(logger: Logger, csharpierProcessProvider: CSharpierProcessProvider) {
this.logger = logger;
this.csharpierProcessProvider = csharpierProcessProvider;

constructor(private readonly formatDocumentProvider: FormatDocumentProvider) {
languages.registerDocumentFormattingEditProvider("csharp", {
provideDocumentFormattingEdits: this.provideDocumentFormattingEdits,
});

languages.registerDocumentRangeFormattingEditProvider("csharp", {
provideDocumentRangeFormattingEdits: this.provideDocumentRangeFormattingEdits,
});
}

private provideDocumentFormattingEdits = async (document: TextDocument) => {
const csharpierProcess = this.csharpierProcessProvider.getProcessFor(document.fileName);
private provideDocumentRangeFormattingEdits = async (
document: TextDocument,
range: Range,
): Promise<TextEdit[]> => {
const differences = await this.getDifferences(document);
const edits: TextEdit[] = [];

for (const difference of differences) {
const diffRange = this.getRange(document, difference);
if (range.contains(diffRange)) {
const textEdit = this.getTextEdit(diffRange, difference);
if (textEdit) {
edits.push(textEdit);
}
}
}

this.logger.info(
"Formatting started for " +
document.fileName +
" using CSharpier " +
csharpierProcess.getVersion(),
);
const startTime = performance.now();
const text = document.getText();
return edits;
};

const updateText = (newText: string) => {
return [TextEdit.replace(FormattingService.fullDocumentRange(document), newText)];
};
private getTextEdit(range: Range, difference: Difference) {
if (difference.operation === generateDifferences.INSERT) {
return TextEdit.insert(
new Position(range.start.line, range.start.character),
difference.insertText!,
);
} else if (difference.operation === generateDifferences.REPLACE) {
return TextEdit.replace(range, difference.insertText!);
} else if (difference.operation === generateDifferences.DELETE) {
return TextEdit.delete(range);
}
}

if ("formatFile2" in csharpierProcess) {
const parameter = {
fileContents: text,
fileName: document.fileName,
};
const result = await csharpierProcess.formatFile2(parameter);
private async getDifferences(document: TextDocument) {
const source = document.getText();
const formattedSource =
(await this.formatDocumentProvider.formatDocument(document)) ?? source;
return generateDifferences(source, formattedSource);
}

this.logger.info("Formatted in " + (performance.now() - startTime) + "ms");
private getRange(document: TextDocument, difference: Difference): Range {
if (difference.operation === generateDifferences.INSERT) {
const start = document.positionAt(difference.offset);
return new Range(start.line, start.character, start.line, start.character);
}
const start = document.positionAt(difference.offset);
const end = document.positionAt(difference.offset + difference.deleteText!.length);
return new Range(start.line, start.character, end.line, end.character);
}

if (result == null) {
return;
}
private provideDocumentFormattingEdits = async (document: TextDocument) => {
const updateText = (newText: string) => {
return [TextEdit.replace(FormattingService.fullDocumentRange(document), newText)];
};

switch (result.status) {
case "Formatted":
return updateText(result.formattedFile);
case "Ignored":
this.logger.info("File is ignored by csharpier cli.");
break;
case "Failed":
this.logger.warn(
"CSharpier cli failed to format the file and returned the following error: " +
result.errorMessage,
);
break;
default:
this.logger.warn("Didn't handle " + result.status);
break;
}
} else {
const newText = await csharpierProcess.formatFile(text, document.fileName);
const endTime = performance.now();
this.logger.info("Formatted in " + (endTime - startTime) + "ms");
if (!newText || newText === text) {
this.logger.debug(
"Skipping write because " + !newText
? "result is empty"
: "current document equals result",
);
return [];
}
const formattedSource = await this.formatDocumentProvider.formatDocument(document);

return updateText(newText);
if (formattedSource) {
return updateText(formattedSource);
}

return [];
Expand Down

0 comments on commit 89e077f

Please sign in to comment.