Skip to content

Commit

Permalink
Supporting csharpier server for 0.28.0+ (#1236)
Browse files Browse the repository at this point in the history
closes #1109
  • Loading branch information
belav authored Apr 20, 2024
1 parent a2a547a commit 5d898a3
Show file tree
Hide file tree
Showing 11 changed files with 1,451 additions and 1,507 deletions.
4 changes: 4 additions & 0 deletions Src/CSharpier.VSCode/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [1.7.0]
- Use CSharpier Http Server for 0.28.0+
- Log version of CSharpier used to format a given file

## [1.6.0]
- Better support for dotnet commands. The extension will now try to locate dotnet by doing the following.
- If `dotnet.dotnetPath` is set, will try using that to find `dotnet`
Expand Down
2,630 changes: 1,168 additions & 1,462 deletions Src/CSharpier.VSCode/package-lock.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions Src/CSharpier.VSCode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "csharpier-vscode",
"displayName": "CSharpier - Code formatter",
"description": "Code formatter using csharpier",
"version": "1.6.0",
"version": "1.7.0",
"publisher": "csharpier",
"author": "CSharpier",
"homepage": "https://marketplace.visualstudio.com/items?itemName=csharpier.csharpier-vscode",
Expand Down Expand Up @@ -69,16 +69,20 @@
"@types/glob": "7.1.4",
"@types/mocha": "9.0.0",
"@types/node": "14.x",
"@types/node-fetch": "^2.6.11",
"@types/semver": "7.3.9",
"@types/vscode": "1.60.0",
"prettier": "2.4.1",
"rimraf": "3.0.2",
"semver": "7.6.0",
"ts-loader": "9.2.5",
"typescript": "4.4.3",
"vsce": "2.15.0",
"vsce": "^1.97.0",
"webpack": "5.90.1",
"webpack-cli": "4.9.1",
"xml-js": "1.6.11"
},
"dependencies": {
"node-fetch": "^2.7.0"
}
}
17 changes: 14 additions & 3 deletions Src/CSharpier.VSCode/src/CSharpierProcessPipeMultipleFiles.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
import { Logger } from "./Logger";
import { ICSharpierProcess } from "./CSharpierProcess";
import { ICSharpierProcess } from "./ICSharpierProcess";
import { getDotNetRoot } from "./DotNetProvider";
import * as process from "process";

export class CSharpierProcessPipeMultipleFiles implements ICSharpierProcess {
private process: ChildProcessWithoutNullStreams;
private callbacks: ((result: string) => void)[] = [];
private logger: Logger;
private nextFile: string = "";
public processFailedToStart = false;
private processFailedToStart = false;
private version: string;

constructor(logger: Logger, csharpierPath: string, workingDirectory: string) {
constructor(logger: Logger, csharpierPath: string, workingDirectory: string, version: string) {
this.logger = logger;
this.process = this.spawnProcess(csharpierPath, workingDirectory);
this.version = version;

this.logger.debug("Warm CSharpier with initial format");
// warm by formatting a file twice, the 3rd time is when it gets really fast
Expand All @@ -21,6 +24,10 @@ export class CSharpierProcessPipeMultipleFiles implements ICSharpierProcess {
});
}

getProcessFailedToStart(): boolean {
return this.processFailedToStart;
}

private spawnProcess = (csharpierPath: string, workingDirectory: string) => {
const csharpierProcess = spawn(csharpierPath, ["--pipe-multiple-files"], {
stdio: "pipe",
Expand Down Expand Up @@ -96,4 +103,8 @@ export class CSharpierProcessPipeMultipleFiles implements ICSharpierProcess {
(this.process.stdin as any).pause();
this.process.kill();
}

getVersion(): string {
return this.version;
}
}
47 changes: 32 additions & 15 deletions Src/CSharpier.VSCode/src/CSharpierProcessProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { Logger } from "./Logger";
import * as path from "path";
import * as semver from "semver";
import * as convert from "xml-js";
import { ICSharpierProcess, NullCSharpierProcess } from "./CSharpierProcess";
import { ICSharpierProcess } from "./ICSharpierProcess";
import { CSharpierProcessSingleFile } from "./CSharpierProcessSingleFile";
import { CSharpierProcessPipeMultipleFiles } from "./CSharpierProcessPipeMultipleFiles";
import * as fs from "fs";
import { InstallerService } from "./InstallerService";
import { CustomPathInstaller } from "./CustomPathInstaller";
import { execDotNet } from "./DotNetProvider";
import { NullCSharpierProcess } from "./NullCSharpierProcess";
import { CSharpierProcessServer } from "./CSharpierProcessServer";
import { ICSharpierProcess2 } from "./ICSharpierProcess";

export class CSharpierProcessProvider implements Disposable {
warnedForOldVersion = false;
Expand All @@ -18,7 +21,10 @@ export class CSharpierProcessProvider implements Disposable {
installerService: InstallerService;
warmingByDirectory: Record<string, boolean | undefined> = {};
csharpierVersionByDirectory: Record<string, string | undefined> = {};
csharpierProcessesByVersion: Record<string, ICSharpierProcess | undefined> = {};
csharpierProcessesByVersion: Record<
string,
ICSharpierProcess | ICSharpierProcess2 | undefined
> = {};

constructor(logger: Logger, extension: Extension<unknown>) {
this.logger = logger;
Expand Down Expand Up @@ -62,7 +68,7 @@ export class CSharpierProcessProvider implements Disposable {
delete this.warmingByDirectory[directory];
}

public getProcessFor = (filePath: string) => {
public getProcessFor = (filePath: string): ICSharpierProcess | ICSharpierProcess2 => {
const directory = this.getDirectoryOfFile(filePath);
let version = this.csharpierVersionByDirectory[directory];
if (!version) {
Expand Down Expand Up @@ -218,26 +224,37 @@ export class CSharpierProcessProvider implements Disposable {

this.logger.debug(`Adding new version ${version} process for ${directory}`);

if (semver.lt(version, "0.12.0")) {
let csharpierProcess: ICSharpierProcess;

if (semver.gte(version, "0.28.0")) {
csharpierProcess = new CSharpierProcessServer(
this.logger,
customPath,
directory,
version,
);
} else if (semver.gte(version, "0.12.0")) {
csharpierProcess = new CSharpierProcessPipeMultipleFiles(
this.logger,
customPath,
directory,
version,
);
} else {
if (!this.warnedForOldVersion) {
window.showInformationMessage(
"Please upgrade to CSharpier >= 0.12.0 for bug fixes and improved formatting speed.",
);
this.warnedForOldVersion = true;
}
return new CSharpierProcessSingleFile(this.logger, customPath);
} else {
const csharpierProcess = new CSharpierProcessPipeMultipleFiles(
this.logger,
customPath,
directory,
);
if (csharpierProcess.processFailedToStart) {
this.displayFailureMessage();
}
csharpierProcess = new CSharpierProcessSingleFile(this.logger, customPath, version);
}

return csharpierProcess;
if (csharpierProcess.getProcessFailedToStart()) {
this.displayFailureMessage();
}

return csharpierProcess;
} catch (ex: any) {
this.logger.error(ex.output.toString());
this.logger.debug(
Expand Down
119 changes: 119 additions & 0 deletions Src/CSharpier.VSCode/src/CSharpierProcessServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
import { Logger } from "./Logger";
import { FormatFileParameter, FormatFileResult, ICSharpierProcess2 } from "./ICSharpierProcess";
import fetch from "node-fetch";

export class CSharpierProcessServer implements ICSharpierProcess2 {
private csharpierPath: string;
private logger: Logger;
private port: number = 0;
private process: ChildProcessWithoutNullStreams | undefined;
private processFailedToStart = false;
private version: string;

constructor(logger: Logger, csharpierPath: string, workingDirectory: string, version: string) {
this.logger = logger;
this.csharpierPath = csharpierPath;
this.spawnProcess(csharpierPath, workingDirectory);
this.version = version;

this.logger.debug("Warm CSharpier with initial format");
// warm by formatting a file twice, the 3rd time is when it gets really fast
this.formatFile("public class ClassName { }", "/Temp/Test.cs").then(() => {
this.formatFile("public class ClassName { }", "/Temp/Test.cs");
});
}

getProcessFailedToStart(): boolean {
return this.processFailedToStart;
}

private spawnProcess(csharpierPath: string, workingDirectory: string) {
const csharpierProcess = spawn(csharpierPath, ["--server"], {
stdio: "pipe",
cwd: workingDirectory,
env: { ...process.env, DOTNET_NOLOGO: "1" },
});

csharpierProcess.on("error", data => {
this.logger.warn(
"Failed to spawn the needed csharpier process. Formatting cannot occur.",
data,
);
this.processFailedToStart = true;
});

let output = "";
const regex = /^Started on (\d+)/;

csharpierProcess.stdout.on("data", chunk => {
output += chunk;
if (regex.test(output) && this.port === 0) {
this.port = parseInt(output.match(regex)![1], 10);
this.logger.debug("Connecting via port " + this.port);
this.process = csharpierProcess;
}
});
}

public async formatFile(content: string, filePath: string): Promise<string> {
const parameter = {
fileName: filePath,
fileContents: content,
};
const result = await this.formatFile2(parameter);
return result?.formattedFile ?? "";
}

public async formatFile2(parameter: FormatFileParameter): Promise<FormatFileResult | null> {
if (this.processFailedToStart) {
this.logger.warn("CSharpier process failed to start. Formatting cannot occur.");
return null;
}

if (typeof this.process === "undefined") {
await new Promise(r => setTimeout(r, 2000));
}

if (this.processFailedToStart || typeof this.process === "undefined") {
this.logger.warn("CSharpier process failed to start. Formatting cannot occur.");
return null;
}

try {
const url = "http://localhost:" + this.port + "/format";

const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(parameter),
});

if (response.status !== 200) {
this.logger.warn(
"Csharpier server returned non-200 status code of " + response.status,
);
return null;
}

return await response.json();
} catch (e) {
this.logger.warn("Failed posting to the csharpier server. " + e);
}

return null;
}

dispose() {
if (typeof this.process !== "undefined") {
(this.process.stdin as any).pause();
this.process.kill();
}
}

getVersion(): string {
return this.version;
}
}
15 changes: 13 additions & 2 deletions Src/CSharpier.VSCode/src/CSharpierProcessSingleFile.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { Logger } from "./Logger";
import { spawn } from "child_process";
import { ICSharpierProcess } from "./CSharpierProcess";
import { ICSharpierProcess } from "./ICSharpierProcess";
import * as path from "path";
import { getDotNetRoot } from "./DotNetProvider";
import * as process from "process";

export class CSharpierProcessSingleFile implements ICSharpierProcess {
private readonly csharpierPath: string;
private logger: Logger;
private version: string;

constructor(logger: Logger, csharpierPath: string) {
constructor(logger: Logger, csharpierPath: string, version: string) {
this.logger = logger;
this.csharpierPath = csharpierPath;
this.version = version;
}

getProcessFailedToStart(): boolean {
return false;
}

formatFile(content: string, filePath: string): Promise<string> {
Expand Down Expand Up @@ -40,4 +47,8 @@ export class CSharpierProcessSingleFile implements ICSharpierProcess {
}

dispose() {}

getVersion(): string {
return this.version;
}
}
2 changes: 1 addition & 1 deletion Src/CSharpier.VSCode/src/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { ExtensionContext, window, workspace } from "vscode";
import { CSharpierProcessProvider } from "./CSharpierProcessProvider";
import { FormattingService } from "./FormattingService";
import { Logger } from "./Logger";
import { NullCSharpierProcess } from "./CSharpierProcess";
import { findDotNet } from "./DotNetProvider";
import { options } from "./Options";
import { NullCSharpierProcess } from "./NullCSharpierProcess";

export async function activate(context: ExtensionContext) {
if (!workspace.isTrusted) {
Expand Down
Loading

0 comments on commit 5d898a3

Please sign in to comment.