Skip to content

Commit

Permalink
Finishing up changes to support local csharpier + install local or gl…
Browse files Browse the repository at this point in the history
…obal version
  • Loading branch information
belav committed Jan 8, 2022
1 parent 9a69528 commit 132dd40
Show file tree
Hide file tree
Showing 14 changed files with 243 additions and 236 deletions.
24 changes: 0 additions & 24 deletions .github/workflows/validate_pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,3 @@ jobs:
dotnet-version: '6.0.x'
- run: |
dotnet build Src/CSharpier.MsBuild/CSharpier.MsBuild.csproj
# run_vscode_tests:
# runs-on: ${{ matrix.os }}
# strategy:
# matrix:
# os: [ ubuntu-latest ] #, windows-latest, macos-latest ]
# name: Test VSCode on ${{ matrix.os }}
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v2
# with:
# node-version: "14"
# - run: /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & echo "Started xvfb"
# shell: bash
# if: ${{ success() && matrix.os == 'ubuntu-latest' }}
# - uses: actions/setup-dotnet@v1
# with:
# dotnet-version: '6.0.x'
# - run: |
# dotnet build CSharpier.sln -c release
# cd Src/CSharpier.VSCode
# npm ci
# npm test
# env:
# DISPLAY: ":99.0"
5 changes: 3 additions & 2 deletions Src/CSharpier.VSCode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ext install csharpier.csharpier-vscode
```

## Usage

### Keyboard Shortcuts

Visual Studio Code provides [default keyboard shortcuts](https://code.visualstudio.com/docs/getstarted/keybindings#_keyboard-shortcuts-reference) for code formatting. You can learn about these for each platform in the [VS Code documentation](https://code.visualstudio.com/docs/getstarted/keybindings#_keyboard-shortcuts-reference).
Expand All @@ -23,7 +24,7 @@ If you don't like the defaults, you can rebind `editor.action.formatDocument` an

### Format On Save

Respects `editor.formatOnSave` setting.
Respects `editor.formatOnSave` setting.

Found in the settings at Text Editor | Formatting | Format on Save

Expand All @@ -39,7 +40,7 @@ You can turn on format-on-save on a per-language basis by scoping the setting:
```

## Limitations

This extension currently only works with a globally installed version of csharpier. See [this issue](https://github.com/belav/csharpier/issues/493) for details.

Format Selection is not supported.

4 changes: 2 additions & 2 deletions Src/CSharpier.VSCode/src/CSharpierProcess.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Disposable } from "vscode";

export interface ICSharpierProcess extends Disposable {
formatFile(content: string, fileName: string): Promise<string>;
formatFile(content: string, filePath: string): Promise<string>;
}

export class NullCSharpierProcess implements ICSharpierProcess {
formatFile(content: string, fileName: string): Promise<string> {
formatFile(content: string, filePath: string): Promise<string> {
return Promise.resolve("");
}

Expand Down
17 changes: 8 additions & 9 deletions Src/CSharpier.VSCode/src/CSharpierProcessPipeMultipleFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ export class CSharpierProcessPipeMultipleFiles implements ICSharpierProcess {
}

private spawnProcess = (csharpierPath: string, workingDirectory: string) => {
const csharpierProcess = spawn("dotnet", [csharpierPath, "--pipe-multiple-files"], {
let csharpierProcess = spawn("dotnet", [csharpierPath, "--pipe-multiple-files"], {
stdio: "pipe",
cwd: workingDirectory,
});

csharpierProcess.stderr.on("data", chunk => {
this.loggingService.logInfo("Got error: " + chunk.toString());
const callback = this.callbacks.shift();
let callback = this.callbacks.shift();
if (callback) {
callback("");
}
Expand All @@ -37,12 +37,11 @@ export class CSharpierProcessPipeMultipleFiles implements ICSharpierProcess {
this.loggingService.logDebug("Got chunk");
this.nextFile += chunk;
let number = this.nextFile.indexOf("\u0003");
if (number >= 0)
{
if (number >= 0) {
this.loggingService.logDebug("Got last chunk");
const result = this.nextFile.substring(0, number)
this.nextFile = this.nextFile.substring(number + 1)
const callback = this.callbacks.shift();
let result = this.nextFile.substring(0, number);
this.nextFile = this.nextFile.substring(number + 1);
let callback = this.callbacks.shift();
if (callback) {
callback(result);
}
Expand All @@ -52,8 +51,8 @@ export class CSharpierProcessPipeMultipleFiles implements ICSharpierProcess {
return csharpierProcess;
};

formatFile(content: string, fileName: string): Promise<string> {
this.process.stdin.write(fileName);
formatFile(content: string, filePath: string): Promise<string> {
this.process.stdin.write(filePath);
this.process.stdin.write("\u0003");
this.process.stdin.write(content);
this.process.stdin.write("\u0003");
Expand Down
149 changes: 98 additions & 51 deletions Src/CSharpier.VSCode/src/CSharpierProcessProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,35 @@ import { execSync } from "child_process";
import { ICSharpierProcess, NullCSharpierProcess } from "./CSharpierProcess";
import { CSharpierProcessSingleFile } from "./CSharpierProcessSingleFile";
import { CSharpierProcessPipeMultipleFiles } from "./CSharpierProcessPipeMultipleFiles";
import * as fs from "fs";
import { InstallerService } from "./InstallerService";

export class CSharpierProcessProvider implements Disposable {

warnedForOldVersion = false;
loggingService: LoggingService;
installerService: InstallerService;
csharpierPath: string;
warmingByDirectory: Record<string, boolean | undefined> = {};
csharpierVersionByDirectory: Record<string, string | undefined> = {};
csharpierProcessesByVersion: Record<string, ICSharpierProcess | undefined> = {};

constructor(loggingService: LoggingService) {
this.loggingService = loggingService;
this.installerService = new InstallerService(
this.loggingService,
this.killRunningProcesses,
);

let timeoutHandle: NodeJS.Timeout;
const setupKillRunningProcesses = () => {
let setupKillRunningProcesses = () => {
// TODO we can't detect when the terminal gets focused
// see https://github.com/microsoft/vscode/issues/117980
// and we can't detect when the text editor itself loses focus
// so in order to make sure someone can use the terminal to update csharpier
// we have to kill off the background process after a set amount of time
clearTimeout(timeoutHandle);
timeoutHandle = setTimeout(this.killRunningProcesses, 15000);
}
};

window.onDidChangeWindowState(event => {
if (!event.focused) {
Expand All @@ -45,7 +52,6 @@ export class CSharpierProcessProvider implements Disposable {
if (event.document.languageId !== "csharp") {
return;
}
this.loggingService.logDebug(JSON.stringify(window.activeTerminal));
this.findAndWarmProcess(event.document.fileName);
clearTimeout(timeoutHandle);
setupKillRunningProcesses();
Expand All @@ -56,18 +62,18 @@ export class CSharpierProcessProvider implements Disposable {
this.loggingService.logDebug("Using command dotnet " + this.csharpierPath);
}

private findAndWarmProcess(fileName: string) {
const directory = path.parse(fileName).dir;
this.loggingService.logDebug("Find and warm for " + directory);
private findAndWarmProcess(filePath: string) {
let directory = path.parse(filePath).dir;
if (this.warmingByDirectory[directory]) {
return;
}
this.loggingService.logDebug("Ensure there is a csharpier process for " + directory);
this.warmingByDirectory[directory] = true;
let version = this.csharpierVersionByDirectory[directory];
if (!version) {
version = this.getCSharpierVersion(directory);
if (!semver.valid(version)) {
this.displayInstallNeededMessage();
this.installerService.displayInstallNeededMessage(directory);
}
this.csharpierVersionByDirectory[directory] = version;
}
Expand All @@ -77,19 +83,18 @@ export class CSharpierProcessProvider implements Disposable {
directory,
version,
);
this.loggingService.logDebug("Adding new process for " + directory);
}
delete this.warmingByDirectory[directory];
}

private getCSharpierPath = () => {
let csharpierPath = "csharpier";

// const csharpierDebugPath = path.resolve(
// let csharpierDebugPath = path.resolve(
// __dirname,
// "../../CSharpier.Cli/bin/Debug/net6.0/dotnet-csharpier.dll",
// );
// const csharpierReleasePath = csharpierDebugPath.replace("Debug", "Release");
// let csharpierReleasePath = csharpierDebugPath.replace("Debug", "Release");
//
// if (fs.existsSync(csharpierDebugPath)) {
// csharpierPath = csharpierDebugPath;
Expand All @@ -99,42 +104,99 @@ export class CSharpierProcessProvider implements Disposable {
return csharpierPath;
};

getProcessFor(fileName: string) {
const directory = path.parse(fileName).dir;
public getProcessFor = (filePath: string) => {
let directory = path.parse(filePath).dir;
let version = this.csharpierVersionByDirectory[directory];
if (!version) {
this.findAndWarmProcess(fileName);
this.findAndWarmProcess(filePath);
version = this.csharpierVersionByDirectory[directory];
}

if (!version || !this.csharpierProcessesByVersion[version])
{
if (!version || !this.csharpierProcessesByVersion[version]) {
// this shouldn't really happen, but just in case
return new NullCSharpierProcess();
}

return this.csharpierProcessesByVersion[version]!;
}
};

private getCSharpierVersion = (directoryThatContainsFile: string): string => {
let currentDirectory = directoryThatContainsFile;
while (true) {
let dotnetToolsPath = path.join(currentDirectory, ".config/dotnet-tools.json");
this.loggingService.logDebug(`Looking for ${dotnetToolsPath}`);
if (fs.existsSync(dotnetToolsPath)) {
let data = JSON.parse(fs.readFileSync(dotnetToolsPath).toString());
let version = data.tools.csharpier?.version;
if (version) {
this.loggingService.logDebug(
"Found version " + version + " in " + dotnetToolsPath,
);
return version;
}
}

let nextDirectory = path.join(currentDirectory, "..");
if (nextDirectory === currentDirectory) {
break;
}
currentDirectory = nextDirectory;
}

this.loggingService.logDebug(
"Unable to find dotnet-tools.json, falling back to running dotnet csharpier --version",
);

let outputFromCsharpier: string;

try {
outputFromCsharpier = execSync(`dotnet ${this.csharpierPath} --version`, {
cwd: directoryThatContainsFile,
}).toString();
} catch (error: any) {
this.loggingService.logDebug(
"dotnet csharpier --version failed with " + error.stderr.toString(),
);
return "";
}

this.loggingService.logDebug(`dotnet csharpier --version output ${outputFromCsharpier}`);

private getCSharpierVersion = (directory: string) => {
const version = execSync("dotnet " + this.csharpierPath + " --version", {
cwd: directory,
})
.toString()
.trim();
return version;
let lines = outputFromCsharpier.split(/\r?\n/);

// sometimes .net outputs more than just the version
for (let x = lines.length - 1; x >= 0; x--) {
let version = lines[x].trim();
if (version !== "") {
return version;
}
}

this.loggingService.logDebug(
"Could not find version in output from dotnet csharpier --version in cwd " +
directoryThatContainsFile +
". Output was \n" +
outputFromCsharpier,
);

return "";
};

private setupCSharpierProcess = (directory: string, version: string) => {
try {
if (!semver.valid(version)) {
return new NullCSharpierProcess();
}

this.loggingService.logDebug(`Adding new version ${version} process for ${directory}`);

if (semver.lt(version, "0.12.0")) {
// TODO this should happen once
window.showInformationMessage(
"Please upgrade to CSharpier >= 0.12.0 for bug fixes and improved formatting speed.",
);
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.loggingService, this.csharpierPath);
} else {
return new CSharpierProcessPipeMultipleFiles(
Expand All @@ -149,34 +211,19 @@ export class CSharpierProcessProvider implements Disposable {
}
};

private displayInstallNeededMessage = () => {
this.loggingService.logError("CSharpier not found");

// TODO deal with this somehow
// window
// .showErrorMessage("CSharpier must be installed globally.", "Install CSharpier")
// .then(selection => {
// if (selection === "Install CSharpier") {
// const command = "dotnet tool install -g csharpier";
// this.loggingService.logInfo("Running " + command);
// const output = execSync(command).toString();
// this.loggingService.logInfo(output);
// this.csharpierProcess = this.setupCSharpierProcess();
// }
// });
};

dispose() {
public dispose = () => {
this.killRunningProcesses();
}
};

killRunningProcesses() {
for (const key in this.csharpierProcessesByVersion) {
this.loggingService.logDebug("disposing of process for version " + key);
private killRunningProcesses = () => {
for (let key in this.csharpierProcessesByVersion) {
this.loggingService.logDebug(
"disposing of process for version " + (key === "" ? "null" : key),
);
this.csharpierProcessesByVersion[key]?.dispose();
}
this.warmingByDirectory = {};
this.csharpierVersionByDirectory = {};
this.csharpierProcessesByVersion = {};
}
};
}
6 changes: 3 additions & 3 deletions Src/CSharpier.VSCode/src/CSharpierProcessSingleFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export class CSharpierProcessSingleFile implements ICSharpierProcess {
this.csharpierPath = csharpierPath;
}

formatFile(content: string, fileName: string): Promise<string> {
const directory = path.parse(fileName).dir;
formatFile(content: string, filePath: string): Promise<string> {
let directory = path.parse(filePath).dir;
return new Promise((resolve, reject) => {
const csharpier = spawn("dotnet", [this.csharpierPath, "--write-stdout"], {
let csharpier = spawn("dotnet", [this.csharpierPath, "--write-stdout"], {
stdio: "pipe",
cwd: directory,
});
Expand Down
Loading

0 comments on commit 132dd40

Please sign in to comment.