diff --git a/Src/CSharpier.VSCode/package-lock.json b/Src/CSharpier.VSCode/package-lock.json index 0698278ae..1046a3c90 100644 --- a/Src/CSharpier.VSCode/package-lock.json +++ b/Src/CSharpier.VSCode/package-lock.json @@ -1,12 +1,12 @@ { "name": "csharpier-vscode", - "version": "1.3.6", + "version": "1.5.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "csharpier-vscode", - "version": "1.3.6", + "version": "1.5.2", "license": "MIT", "devDependencies": { "@types/glob": "7.1.4", diff --git a/Src/CSharpier.VSCode/src/CSharpierProcessProvider.ts b/Src/CSharpier.VSCode/src/CSharpierProcessProvider.ts index 4c6174400..1127083aa 100644 --- a/Src/CSharpier.VSCode/src/CSharpierProcessProvider.ts +++ b/Src/CSharpier.VSCode/src/CSharpierProcessProvider.ts @@ -2,7 +2,6 @@ import { Disposable, Extension, TextEditor, window, workspace } from "vscode"; import { Logger } from "./Logger"; import * as path from "path"; import * as semver from "semver"; -import { execSync } from "child_process"; import * as convert from "xml-js"; import { ICSharpierProcess, NullCSharpierProcess } from "./CSharpierProcess"; import { CSharpierProcessSingleFile } from "./CSharpierProcessSingleFile"; @@ -10,6 +9,7 @@ import { CSharpierProcessPipeMultipleFiles } from "./CSharpierProcessPipeMultipl import * as fs from "fs"; import { InstallerService } from "./InstallerService"; import { CustomPathInstaller } from "./CustomPathInstaller"; +import { ExecDotNet } from "./DotNetProvider"; export class CSharpierProcessProvider implements Disposable { warnedForOldVersion = false; @@ -19,14 +19,17 @@ export class CSharpierProcessProvider implements Disposable { warmingByDirectory: Record = {}; csharpierVersionByDirectory: Record = {}; csharpierProcessesByVersion: Record = {}; + execDotNet: ExecDotNet; - constructor(logger: Logger, extension: Extension) { + constructor(logger: Logger, extension: Extension, execDotNet: ExecDotNet) { this.logger = logger; - this.customPathInstaller = new CustomPathInstaller(logger); + this.execDotNet = execDotNet; + this.customPathInstaller = new CustomPathInstaller(logger, execDotNet); this.installerService = new InstallerService( this.logger, this.killRunningProcesses, extension, + execDotNet, ); window.onDidChangeActiveTextEditor((event: TextEditor | undefined) => { @@ -138,7 +141,7 @@ export class CSharpierProcessProvider implements Disposable { let outputFromCsharpier: string; try { - outputFromCsharpier = execSync(`dotnet csharpier --version`, { + outputFromCsharpier = this.execDotNet(`dotnet csharpier --version`, { cwd: directoryThatContainsFile, env: { ...process.env, DOTNET_NOLOGO: "1" }, }) @@ -146,8 +149,8 @@ export class CSharpierProcessProvider implements Disposable { .trim(); this.logger.debug(`dotnet csharpier --version output: ${outputFromCsharpier}`); - const versionWithoutHash = outputFromCsharpier.split("+")[0] - this.logger.debug(`Using ${versionWithoutHash} as the version number.`) + const versionWithoutHash = outputFromCsharpier.split("+")[0]; + this.logger.debug(`Using ${versionWithoutHash} as the version number.`); return versionWithoutHash; } catch (error: any) { const message = !error.stderr ? error.toString() : error.stderr.toString(); @@ -212,7 +215,7 @@ export class CSharpierProcessProvider implements Disposable { } if (!this.customPathInstaller.ensureVersionInstalled(version)) { - this.logger.debug(`Unable to validate install of version ${version}`) + this.logger.debug(`Unable to validate install of version ${version}`); this.displayFailureMessage(); return NullCSharpierProcess.instance; } @@ -230,7 +233,11 @@ export class CSharpierProcessProvider implements Disposable { } return new CSharpierProcessSingleFile(this.logger, customPath); } else { - const csharpierProcess = new CSharpierProcessPipeMultipleFiles(this.logger, customPath, directory); + const csharpierProcess = new CSharpierProcessPipeMultipleFiles( + this.logger, + customPath, + directory, + ); if (csharpierProcess.processFailedToStart) { this.displayFailureMessage(); } diff --git a/Src/CSharpier.VSCode/src/CustomPathInstaller.ts b/Src/CSharpier.VSCode/src/CustomPathInstaller.ts index 8f61fef98..58cf00264 100644 --- a/Src/CSharpier.VSCode/src/CustomPathInstaller.ts +++ b/Src/CSharpier.VSCode/src/CustomPathInstaller.ts @@ -1,15 +1,17 @@ import { Logger } from "./Logger"; import * as path from "path"; import * as fs from "fs"; -import { execSync } from "child_process"; import { workspace } from "vscode"; +import { ExecDotNet } from "./DotNetProvider"; export class CustomPathInstaller { logger: Logger; customPath: string; + execDotNet: ExecDotNet; - constructor(logger: Logger) { + constructor(logger: Logger, execDotNet: ExecDotNet) { this.logger = logger; + this.execDotNet = execDotNet; this.customPath = workspace.getConfiguration("csharpier").get("dev.customPath") ?? ""; } @@ -38,34 +40,39 @@ export class CustomPathInstaller { const command = `dotnet tool install csharpier --version ${version} --tool-path "${pathToDirectoryForVersion}"`; this.logger.debug("Running " + command); - execSync(command); + this.execDotNet(command); return this.validateInstall(pathToDirectoryForVersion, version); } private validateInstall(pathToDirectoryForVersion: string, version: string): boolean { try { - const output = execSync(`"${this.getPathForVersion(version)}" --version`, { + // TODO this fails if we use dotnetPath, we really need to set it as DOTNET_ROOT + const output = this.execDotNet(`"${this.getPathForVersion(version)}" --version`, { env: { ...process.env, DOTNET_NOLOGO: "1" }, }) .toString() .trim(); this.logger.debug(`"${this.getPathForVersion(version)}" --version output: ${output}`); - const versionWithoutHash = output.split("+")[0] - this.logger.debug(`Using ${versionWithoutHash} as the version number.`) + const versionWithoutHash = output.split("+")[0]; + this.logger.debug(`Using ${versionWithoutHash} as the version number.`); if (versionWithoutHash === version) { this.logger.debug("CSharpier at " + pathToDirectoryForVersion + " already exists"); return true; - } - else { - this.logger.warn("Version of " + versionWithoutHash + " did not match expected version of " + version) + } else { + this.logger.warn( + "Version of " + + versionWithoutHash + + " did not match expected version of " + + version, + ); } } catch (error: any) { const message = !error.stderr ? error.toString() : error.stderr.toString(); this.logger.warn( - "Exception while running 'dotnet csharpier --version' in " + + "Exception while running 'dotnet-csharpier --version' in " + pathToDirectoryForVersion, message, ); diff --git a/Src/CSharpier.VSCode/src/DotNetProvider.ts b/Src/CSharpier.VSCode/src/DotNetProvider.ts new file mode 100644 index 000000000..f258d8252 --- /dev/null +++ b/Src/CSharpier.VSCode/src/DotNetProvider.ts @@ -0,0 +1,127 @@ +import { execSync, ExecSyncOptionsWithBufferEncoding } from "child_process"; +import { options } from "./Options"; +import { Logger } from "./Logger"; +import { getDotnetInfo } from "./vscode-csharp/getDotnetInfo"; +import { findDotNetFromRuntimes } from "./vscode-csharp/findDotNetFromRuntimes"; + +export type ExecDotNet = ( + command: string, + options?: ExecSyncOptionsWithBufferEncoding | undefined, +) => Buffer; + +export const getExecDotNet = async (logger: Logger): Promise => { + return await doDotNetInfoWay(logger); + // + // + // const fileName = process.platform === "win32" ? "dotnet.exe" : "dotnet"; + // const env = { ...process.env }; + // + // // TODO test without this but with a sym link + // // TODO what about using the sh stuff? + // const dotnetPathOption = options.dotnetPath; + // if (dotnetPathOption.length > 0) { + // env.PATH = dotnetPathOption + path.delimiter + env.PATH; + // logger.debug("including " + options.dotnetPath + " in the path to test for dotnet"); + // } + // const dotNetCliPaths = options.dotNetCliPaths; + // + // for (const dotnetPath of dotNetCliPaths) { + // env.PATH = env.PATH + path.delimiter + dotnetPath; + // logger.debug("including " + dotnetPath + " in the path to test for dotnet"); + // } + // + // let result: { stdout: string; stderr: string } = { stdout: "", stderr: "" }; + // + // try { + // result = await promisify(exec)(`${fileName} --version`, { env }); + // } catch (exception) { + // result.stderr = exception as any; + // } + // + // let useSH = false; + // let foundDotnet = true; + // if (result.stderr) { + // logger.warn(`Unable to read dotnet version information. \n ${result.stderr}`); + // useSH = true; + // try { + // result = await promisify(exec)(`sh -c "${fileName} --version"`, { env }); + // } catch (exception) { + // result.stderr = exception as any; + // } + // if (result.stderr) { + // logger.warn( + // `Unable to read dotnet version information using "sh -c". Error ${result.stderr}`, + // ); + // foundDotnet = false; + // } + // } + // + // if (!foundDotnet) { + // return null; + // } + // + // return (command: string, options?: ExecSyncOptionsWithBufferEncoding): Buffer => { + // if (useSH) { + // command = `sh -c "${command}"`; + // } + // + // if (options === undefined) { + // options = {}; + // } + // + // if (options.env === undefined) { + // options.env = { ...process.env }; + // } + // + // options.env.PATH = env.PATH; + // + // return execSync(command, options); + // }; +}; + +// TODO we need to find dotnet via sh if these two options are not set and we can't find it, probably with runDotnetInfo +async function doDotNetInfoWay(logger: Logger) { + const dotNetCliPaths = options.dotNetCliPaths; + const dotnetPathOption = options.dotnetPath; + + const dotnetInfo = await getDotnetInfo([dotnetPathOption, ...dotNetCliPaths]); + logger.info(JSON.stringify(dotnetInfo, null, 4)); + + let dotnetExecutablePath = dotnetInfo.CliPath; + if (!dotnetExecutablePath) { + dotnetExecutablePath = findDotNetFromRuntimes(dotnetInfo); + } + + return (command: string, options?: ExecSyncOptionsWithBufferEncoding): Buffer => { + if (options === undefined) { + options = {}; + } + + return execSync(dotnetExecutablePath + " " + command, options); + }; +} + +/* +bela@ubuntu-two:~/.dotnet$ find /usr -name "dotnet" -type f +bela@ubuntu-two:~/.dotnet$ find ~ -name "dotnet" -type f + +whereis + +which + +install-script put it at with install script seems to go to ~/.dotnet +gh user has it in /usr/bin, which seems like it should be standard + +after running +sudo ln -s ~/.dotnet/dotnet /usr/bin/dotnet +then I get +You must install .NET to run this application. + +but it installed just fine +global install has the same problem + +You must install .NET to run this application. + +was missing DOTNET_ROOT, should try to set it automatically? roslynLanguageServer does + +*/ diff --git a/Src/CSharpier.VSCode/src/Extension.ts b/Src/CSharpier.VSCode/src/Extension.ts index ed7aa9d5c..94c8c5547 100644 --- a/Src/CSharpier.VSCode/src/Extension.ts +++ b/Src/CSharpier.VSCode/src/Extension.ts @@ -3,6 +3,7 @@ import { CSharpierProcessProvider } from "./CSharpierProcessProvider"; import { FormattingService } from "./FormattingService"; import { Logger } from "./Logger"; import { NullCSharpierProcess } from "./CSharpierProcess"; +import { getExecDotNet } from "./DotNetProvider"; export async function activate(context: ExtensionContext) { if (!workspace.isTrusted) { @@ -18,13 +19,24 @@ const initPlugin = async (context: ExtensionContext) => { workspace.getConfiguration("csharpier").get("enableDebugLogs") ?? false; const logger = new Logger(enableDebugLogs); - NullCSharpierProcess.create(logger); - const isDevelopment = (process.env as any).MODE === "development"; + const execDotNet = await getExecDotNet(logger); + if (execDotNet === null) { + logger.error( + "CSharpier was unable to find a way to run 'dotnet' commands. Check your PATH or set dotnet.dotnetPath or omnisharp.dotNetCliPaths", + ); + return; + } + + NullCSharpierProcess.create(logger); logger.info("Initializing " + (process.env as any).EXTENSION_NAME); - const csharpierProcessProvider = new CSharpierProcessProvider(logger, context.extension); + const csharpierProcessProvider = new CSharpierProcessProvider( + logger, + context.extension, + execDotNet, + ); new FormattingService(logger, csharpierProcessProvider); context.subscriptions.push(csharpierProcessProvider); diff --git a/Src/CSharpier.VSCode/src/InstallerService.ts b/Src/CSharpier.VSCode/src/InstallerService.ts index be34adf40..66b7173b9 100644 --- a/Src/CSharpier.VSCode/src/InstallerService.ts +++ b/Src/CSharpier.VSCode/src/InstallerService.ts @@ -1,9 +1,9 @@ import { Logger } from "./Logger"; import { Extension, window, workspace } from "vscode"; -import { execSync } from "child_process"; import * as path from "path"; import * as fs from "fs"; import * as vscode from "vscode"; +import { ExecDotNet } from "./DotNetProvider"; export class InstallerService { rejectedError = false; @@ -13,11 +13,18 @@ export class InstallerService { logger: Logger; killRunningProcesses: () => void; extension: Extension; + execDotNet: ExecDotNet; - constructor(logger: Logger, killRunningProcesses: () => void, extension: Extension) { + constructor( + logger: Logger, + killRunningProcesses: () => void, + extension: Extension, + execDotNet: ExecDotNet, + ) { this.logger = logger; this.killRunningProcesses = killRunningProcesses; this.extension = extension; + this.execDotNet = execDotNet; } public displayInstallNeededMessage = (directoryThatContainsFile: string) => { @@ -72,7 +79,7 @@ export class InstallerService { if (selection === globalButton) { const command = "dotnet tool install -g csharpier"; this.logger.info("Installing csharpier globally with " + command); - const output = execSync(command).toString(); + const output = this.execDotNet(command).toString(); this.logger.info(output); } else if (selection === localButton) { if (solutionRoot) { @@ -83,9 +90,9 @@ export class InstallerService { ); this.logger.info("Installing csharpier in " + manifestPath); if (!fs.existsSync(manifestPath)) { - execSync("dotnet new tool-manifest", { cwd: solutionRoot }); + this.execDotNet("dotnet new tool-manifest", { cwd: solutionRoot }); } - execSync("dotnet tool install csharpier", { cwd: solutionRoot }); + this.execDotNet("dotnet tool install csharpier", { cwd: solutionRoot }); } catch (error) { this.logger.error("Installing failed with ", error); } diff --git a/Src/CSharpier.VSCode/src/Options.ts b/Src/CSharpier.VSCode/src/Options.ts new file mode 100644 index 000000000..2106b6f6a --- /dev/null +++ b/Src/CSharpier.VSCode/src/Options.ts @@ -0,0 +1,42 @@ +import * as vscode from "vscode"; + +export interface Options { + readonly dotNetCliPaths: string[]; + readonly dotnetPath: string; +} + +class OptionsImpl implements Options { + public get dotNetCliPaths() { + return readOption("omnisharp.dotNetCliPaths", []); + } + public get dotnetPath() { + return readOption("dotnet.dotnetPath", "", "omnisharp.dotnetPath"); + } +} + +export const options: Options = new OptionsImpl(); + +function readOptionFromConfig( + config: vscode.WorkspaceConfiguration, + option: string, + defaultValue: T, + ...backCompatOptionNames: string[] +): T { + let value = config.get(option); + + if (value === undefined && backCompatOptionNames.length > 0) { + // Search the back compat options for a defined value. + value = backCompatOptionNames.map(name => config.get(name)).find(val => val); + } + + return value ?? defaultValue; +} + +function readOption(option: string, defaultValue: T, ...backCompatOptionNames: string[]): T { + return readOptionFromConfig( + vscode.workspace.getConfiguration(), + option, + defaultValue, + ...backCompatOptionNames, + ); +} diff --git a/Src/CSharpier.VSCode/src/vscode-csharp/LICENSE.txt b/Src/CSharpier.VSCode/src/vscode-csharp/LICENSE.txt new file mode 100644 index 000000000..b64d1f6bc --- /dev/null +++ b/Src/CSharpier.VSCode/src/vscode-csharp/LICENSE.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) .NET Foundation and Contributors +All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Src/CSharpier.VSCode/src/vscode-csharp/dotnetinfo.ts b/Src/CSharpier.VSCode/src/vscode-csharp/dotnetinfo.ts new file mode 100644 index 000000000..0e82036b0 --- /dev/null +++ b/Src/CSharpier.VSCode/src/vscode-csharp/dotnetinfo.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as semver from "semver"; + +type RuntimeVersionMap = { [runtime: string]: RuntimeInfo[] }; +export interface DotnetInfo { + CliPath?: string; + FullInfo: string; + Version: string; + /* a runtime-only install of dotnet will not output a runtimeId in dotnet --info. */ + RuntimeId?: string; + Architecture?: string; + Runtimes: RuntimeVersionMap; +} + +export interface RuntimeInfo { + Version: semver.SemVer; + Path: string; +} diff --git a/Src/CSharpier.VSCode/src/vscode-csharp/findDotNetFromRuntimes.ts b/Src/CSharpier.VSCode/src/vscode-csharp/findDotNetFromRuntimes.ts new file mode 100644 index 000000000..5cc9d985d --- /dev/null +++ b/Src/CSharpier.VSCode/src/vscode-csharp/findDotNetFromRuntimes.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DotnetInfo, RuntimeInfo } from "./dotnetinfo"; +import * as semver from "semver"; +import * as path from "path"; +import { existsSync } from "fs"; + +export function findDotNetFromRuntimes(dotnetInfo: DotnetInfo) { + const requiredRuntimeVersion = "6.0.0"; + + const coreRuntimeVersions = dotnetInfo.Runtimes["Microsoft.NETCore.App"]; + let matchingRuntime: RuntimeInfo | undefined = undefined; + for (const runtime of coreRuntimeVersions) { + // We consider a match if the runtime is greater than or equal to the required version since we roll forward. + if (semver.gte(runtime.Version, requiredRuntimeVersion)) { + matchingRuntime = runtime; + break; + } + } + + if (!matchingRuntime) { + throw new Error( + `No compatible .NET runtime found. Minimum required version is ${requiredRuntimeVersion}.`, + ); + } + + // The .NET install layout is a well known structure on all platforms. + // See https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md#net-core-install-layout + // + // Therefore we know that the runtime path is always in /shared/ + // and the dotnet executable is always at /dotnet(.exe). + // + // Since dotnet --list-runtimes will always use the real assembly path to output the runtime folder (no symlinks!) + // we know the dotnet executable will be two folders up in the install root. + const runtimeFolderPath = matchingRuntime.Path; + const installFolder = path.dirname(path.dirname(runtimeFolderPath)); + const dotnetExecutablePath = path.join( + installFolder, + process.platform === "win32" ? "dotnet.exe" : "dotnet", + ); + if (!existsSync(dotnetExecutablePath)) { + throw new Error( + `dotnet executable path does not exist: ${dotnetExecutablePath}, dotnet installation may be corrupt.`, + ); + } + return dotnetExecutablePath; +} diff --git a/Src/CSharpier.VSCode/src/vscode-csharp/getDotnetInfo.ts b/Src/CSharpier.VSCode/src/vscode-csharp/getDotnetInfo.ts new file mode 100644 index 000000000..66cc0caec --- /dev/null +++ b/Src/CSharpier.VSCode/src/vscode-csharp/getDotnetInfo.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as semver from "semver"; +import { join } from "path"; +import { EOL } from "os"; +import { DotnetInfo, RuntimeInfo } from "./dotnetinfo"; +import * as fs from "fs"; +import * as cp from "child_process"; + +// This function calls `dotnet --info` and returns the result as a DotnetInfo object. +export async function getDotnetInfo(dotNetCliPaths: string[]): Promise { + const dotnetExecutablePath = getDotNetExecutablePath(dotNetCliPaths); + + const data = await runDotnetInfo(dotnetExecutablePath); + const dotnetInfo = await parseDotnetInfo(data, dotnetExecutablePath); + return dotnetInfo; +} + +export function getDotNetExecutablePath(dotNetCliPaths: string[]): string | undefined { + const dotnetExeName = process.platform === "win32" ? "dotnet.exe" : "dotnet"; + let dotnetExecutablePath: string | undefined; + + for (const dotnetPath of dotNetCliPaths) { + const dotnetFullPath = join(dotnetPath, dotnetExeName); + if (existsSync(dotnetFullPath)) { + dotnetExecutablePath = dotnetFullPath; + break; + } + } + return dotnetExecutablePath; +} + +async function runDotnetInfo(dotnetExecutablePath: string | undefined): Promise { + try { + const env = { + ...process.env, + DOTNET_CLI_UI_LANGUAGE: "en-US", + }; + const command = dotnetExecutablePath ? `"${dotnetExecutablePath}"` : "dotnet"; + const data = await execChildProcess(`${command} --info`, process.cwd(), env); + return data; + } catch (error) { + const message = error instanceof Error ? error.message : `${error}`; + throw new Error(`Error running dotnet --info: ${message}`); + } +} + +async function parseDotnetInfo( + dotnetInfo: string, + dotnetExecutablePath: string | undefined, +): Promise { + try { + const cliPath = dotnetExecutablePath; + const fullInfo = dotnetInfo; + + let version: string | undefined; + let runtimeId: string | undefined; + let architecture: string | undefined; + + let lines = dotnetInfo.replace(/\r/gm, "").split("\n"); + for (const line of lines) { + let match: RegExpMatchArray | null; + if ((match = /^\s*Version:\s*([^\s].*)$/.exec(line))) { + version = match[1]; + } else if ((match = /^ RID:\s*([\w\-.]+)$/.exec(line))) { + runtimeId = match[1]; + } else if ((match = /^\s*Architecture:\s*(.*)/.exec(line))) { + architecture = match[1]; + } + } + + const runtimeVersions: { [runtime: string]: RuntimeInfo[] } = {}; + const command = dotnetExecutablePath ? `"${dotnetExecutablePath}"` : "dotnet"; + const listRuntimes = await execChildProcess( + `${command} --list-runtimes`, + process.cwd(), + process.env, + ); + lines = listRuntimes.split(/\r?\n/); + for (const line of lines) { + let match: RegExpMatchArray | null; + if ((match = /^([\w.]+) ([^ ]+) \[([^\]]+)\]$/.exec(line))) { + const runtime = match[1]; + const runtimeVersion = match[2]; + if (runtime in runtimeVersions) { + runtimeVersions[runtime].push({ + Version: semver.parse(runtimeVersion)!, + Path: match[3], + }); + } else { + runtimeVersions[runtime] = [ + { + Version: semver.parse(runtimeVersion)!, + Path: match[3], + }, + ]; + } + } + } + + if (version !== undefined) { + const dotnetInfo: DotnetInfo = { + CliPath: cliPath, + FullInfo: fullInfo, + Version: version, + RuntimeId: runtimeId, + Architecture: architecture, + Runtimes: runtimeVersions, + }; + return dotnetInfo; + } + + throw new Error("Failed to parse dotnet version information"); + } catch (error) { + const message = error instanceof Error ? error.message : `${error}`; + throw new Error( + `Error parsing dotnet --info: ${message}, raw info was:${EOL}${dotnetInfo}`, + ); + } +} + +function existsSync(path: string): boolean { + try { + fs.accessSync(path, fs.constants.F_OK); + return true; + } catch (err) { + const error = err as NodeJS.ErrnoException; + if (error.code === "ENOENT" || error.code === "ENOTDIR") { + return false; + } else { + throw Error(error.code); + } + } +} + +async function execChildProcess( + command: string, + workingDirectory: string, + env: NodeJS.ProcessEnv = {}, +): Promise { + return new Promise((resolve, reject) => { + cp.exec( + command, + { cwd: workingDirectory, maxBuffer: 500 * 1024, env: env }, + (error: any, stdout: any, stderr: any) => { + if (error) { + reject( + new Error(`${error} +${stdout} +${stderr}`), + ); + } else if (stderr && !stderr.includes("screen size is bogus")) { + reject(new Error(stderr)); + } else { + resolve(stdout); + } + }, + ); + }); +} diff --git a/Src/CSharpier/CSharpFormatter.cs b/Src/CSharpier/CSharpFormatter.cs index 59e3942ff..c434df07b 100644 --- a/Src/CSharpier/CSharpFormatter.cs +++ b/Src/CSharpier/CSharpFormatter.cs @@ -17,6 +17,8 @@ Task IFormatter.FormatAsync( CancellationToken cancellationToken ) { + + return FormatAsync(code, printerOptions, cancellationToken); } diff --git a/Src/CSharpier/CodeFormatter.cs b/Src/CSharpier/CodeFormatter.cs index 949f1452a..0b711aa9c 100644 --- a/Src/CSharpier/CodeFormatter.cs +++ b/Src/CSharpier/CodeFormatter.cs @@ -11,6 +11,16 @@ public static CodeFormatterResult Format(string code, CodeFormatterOptions? opti return FormatAsync(code, options).Result; } + + + + + + + + + + public static Task FormatAsync( string code, CodeFormatterOptions? options = null, diff --git a/Src/CSharpier/DocSerializer.cs b/Src/CSharpier/DocSerializer.cs index 825c13c8a..1e27b51f2 100644 --- a/Src/CSharpier/DocSerializer.cs +++ b/Src/CSharpier/DocSerializer.cs @@ -13,6 +13,7 @@ public static string Serialize(Doc doc) var result = new StringBuilder(); Serialize(doc, result, 0); return result.ToString(); + } public static void Serialize(Doc doc, StringBuilder result, int indent, Doc? parent = null) diff --git a/docs/Editors.md b/docs/Editors.md index 70b4ba282..71f0b4ed2 100644 --- a/docs/Editors.md +++ b/docs/Editors.md @@ -23,3 +23,6 @@ Use the [official plugin](https://plugins.jetbrains.com/plugin/18243-csharpier) It can be installed via the Plugins dialog. ### Neovim Use [neoformat](https://github.com/sbdchd/neoformat) + +### Emacs +Use [format-all](https://github.com/lassik/emacs-format-all-the-code)