diff --git a/package.json b/package.json index e72c8d1f60..ec02eafe2e 100644 --- a/package.json +++ b/package.json @@ -363,6 +363,16 @@ "default": null, "description": "Specifies the full path to the OmniSharp server." }, + "omnisharp.useLatestExperimentalBuild": { + "type": "boolean", + "default": false, + "description": "Specifies whether the latest experimental omnisharp build must be used" + }, + "omnisharp.alternateVersion": { + "type": "string", + "default": null, + "description": "Specifies which alternate omnisharp version must be used" + }, "omnisharp.useMono": { "type": "boolean", "default": false, @@ -2125,4 +2135,4 @@ } ] } -} +} \ No newline at end of file diff --git a/src/omnisharp/latestVersionSelector.ts b/src/omnisharp/latestVersionSelector.ts new file mode 100644 index 0000000000..7d71f30358 --- /dev/null +++ b/src/omnisharp/latestVersionSelector.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as semver from 'semver'; + +export function GetLatestInstalledExperimentalVersion(basePath: string) { + let latestVersion: string = ''; + if (fs.existsSync(basePath)) { + let installedItems = fs.readdirSync(basePath); + if (installedItems && installedItems.length > 0) { + let validVersions = installedItems.filter(value => semver.valid(value)); + if (validVersions && validVersions.length > 0) { + latestVersion = validVersions.reduce((latestTillNow, element) => { + if (semver.gt(latestTillNow, element)) { + return latestTillNow; + } + + return element; + }); + } + } + } + + return latestVersion; +} \ No newline at end of file diff --git a/src/omnisharp/launcher.ts b/src/omnisharp/launcher.ts index 2a70ac2737..d70206d77a 100644 --- a/src/omnisharp/launcher.ts +++ b/src/omnisharp/launcher.ts @@ -12,6 +12,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import * as util from '../common'; import { Options } from './options'; +import { GetLatestInstalledExperimentalVersion } from './latestVersionSelector'; export enum LaunchTargetKind { Solution, @@ -46,7 +47,7 @@ export function findLaunchTargets(): Thenable { const options = Options.Read(); return vscode.workspace.findFiles( - /*include*/ '{**/*.sln,**/*.csproj,**/project.json,**/*.csx,**/*.cake}', + /*include*/ '{**/*.sln,**/*.csproj,**/project.json,**/*.csx,**/*.cake}', /*exclude*/ '{**/node_modules/**,**/.git/**,**/bower_components/**}', /*maxResults*/ options.maxProjectResults) .then(resourcesToLaunchTargets); @@ -61,7 +62,7 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { // // TODO: // * It should be possible to choose a .csproj as a launch target - // * It should be possible to choose a .sln file even when no .csproj files are found + // * It should be possible to choose a .sln file even when no .csproj files are found // within the root. if (!Array.isArray(resources)) { @@ -88,8 +89,7 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { let targets: LaunchTarget[] = []; - workspaceFolderToUriMap.forEach((resources, folderIndex) => - { + workspaceFolderToUriMap.forEach((resources, folderIndex) => { let hasCsProjFiles = false, hasSlnFile = false, hasProjectJson = false, @@ -98,15 +98,15 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { hasCake = false; hasCsProjFiles = resources.some(isCSharpProject); - + let folder = vscode.workspace.workspaceFolders[folderIndex]; let folderPath = folder.uri.fsPath; - + resources.forEach(resource => { // Add .sln files if there are .csproj files if (hasCsProjFiles && isSolution(resource)) { hasSlnFile = true; - + targets.push({ label: path.basename(resource.fsPath), description: vscode.workspace.asRelativePath(path.dirname(resource.fsPath)), @@ -115,13 +115,13 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { kind: LaunchTargetKind.Solution }); } - + // Add project.json files if (isProjectJson(resource)) { const dirname = path.dirname(resource.fsPath); hasProjectJson = true; hasProjectJsonAtRoot = hasProjectJsonAtRoot || dirname === folderPath; - + targets.push({ label: path.basename(resource.fsPath), description: vscode.workspace.asRelativePath(path.dirname(resource.fsPath)), @@ -130,18 +130,18 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { kind: LaunchTargetKind.ProjectJson }); } - + // Discover if there is any CSX file if (!hasCSX && isCsx(resource)) { hasCSX = true; } - + // Discover if there is any Cake file if (!hasCake && isCake(resource)) { hasCake = true; } }); - + // Add the root folder under the following circumstances: // * If there are .csproj files, but no .sln file, and none in the root. // * If there are project.json files, but none in the root. @@ -154,7 +154,7 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { kind: LaunchTargetKind.Folder }); } - + // if we noticed any CSX file(s), add a single CSX-specific target pointing at the root folder if (hasCSX) { targets.push({ @@ -165,7 +165,7 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { kind: LaunchTargetKind.Csx }); } - + // if we noticed any Cake file(s), add a single Cake-specific target pointing at the root folder if (hasCake) { targets.push({ @@ -229,8 +229,7 @@ function launch(cwd: string, args: string[]): Promise { return PlatformInformation.GetCurrent().then(platformInfo => { const options = Options.Read(); - if (options.useEditorFormattingSettings) - { + if (options.useEditorFormattingSettings) { let globalConfig = vscode.workspace.getConfiguration(); let csharpConfig = vscode.workspace.getConfiguration('[csharp]'); @@ -253,8 +252,18 @@ function launch(cwd: string, args: string[]): Promise { : launchNix(options.path, cwd, args); } - // If the user has not provided a path, we'll use the locally-installed OmniSharp - const basePath = path.resolve(util.getExtensionPath(), '.omnisharp'); + let extensionPath = util.getExtensionPath(); + let basePath: string; + if (options.alternateVersion) { + basePath = path.resolve(extensionPath,`.omnisharp/experimental/${options.alternateVersion}`); + } + else if (options.useLatestExperimentalBuild) { + basePath = getLatestExperimentalBuildPath(extensionPath); + } + else { + // If the user has neither provided a path not set any options for using other versions, we'll use the locally-installed OmniSharp + basePath = path.resolve(extensionPath, '.omnisharp'); + } if (platformInfo.isWindows()) { return launchWindows(path.join(basePath, 'OmniSharp.exe'), cwd, args); @@ -272,13 +281,25 @@ function launch(cwd: string, args: string[]): Promise { }); } +function getLatestExperimentalBuildPath(extensionPath: string) { + let dirPath = path.resolve(extensionPath, `.omnisharp/experimental`); + let latestVersion = GetLatestInstalledExperimentalVersion(dirPath); + if (latestVersion && latestVersion.length>0) { + return path.resolve(extensionPath, `.omnisharp/experimental/${latestVersion}`); + } + else { + //If there is no latest version present in experimental folder, use the release version + return path.resolve(extensionPath, `.omnisharp`); + } +} + function getConfigurationValue(globalConfig: vscode.WorkspaceConfiguration, csharpConfig: vscode.WorkspaceConfiguration, configurationPath: string, defaultValue: any): any { - + if (csharpConfig[configurationPath] != undefined) { return csharpConfig[configurationPath]; } - + return globalConfig.get(configurationPath, defaultValue); } @@ -287,7 +308,7 @@ function launchWindows(launchPath: string, cwd: string, args: string[]): LaunchR const hasSpaceWithoutQuotes = /^[^"].* .*[^"]/; return hasSpaceWithoutQuotes.test(arg) ? `"${arg}"` - : arg.replace("&","^&"); + : arg.replace("&", "^&"); } let argsCopy = args.slice(0); // create copy of args @@ -395,4 +416,4 @@ export function hasMono(range?: string): Promise { resolve(ret); }); }); -} \ No newline at end of file +} diff --git a/src/omnisharp/options.ts b/src/omnisharp/options.ts index 96aa6219de..2b90ecf267 100644 --- a/src/omnisharp/options.ts +++ b/src/omnisharp/options.ts @@ -18,7 +18,9 @@ export class Options { public useFormatting?: boolean, public showReferencesCodeLens?: boolean, public showTestsCodeLens?: boolean, - public disableCodeActions?: boolean) { } + public disableCodeActions?: boolean, + public alternateVersion?: string, + public useLatestExperimentalBuild?: boolean) { } public static Read(): Options { // Extra effort is taken below to ensure that legacy versions of options @@ -59,17 +61,22 @@ export class Options { const disableCodeActions = csharpConfig.get('disableCodeActions', false); - return new Options(path, - useMono, + const alternateVersion = omnisharpConfig.get('alternateVersion'); + const useLatestExperimentalBuild = omnisharpConfig.get('useLatestExperimentalBuild', false); + + return new Options(path, + useMono, waitForDebugger, - loggingLevel, - autoStart, - projectLoadTimeout, - maxProjectResults, - useEditorFormattingSettings, + loggingLevel, + autoStart, + projectLoadTimeout, + maxProjectResults, + useEditorFormattingSettings, useFormatting, showReferencesCodeLens, showTestsCodeLens, - disableCodeActions); + disableCodeActions, + alternateVersion, + useLatestExperimentalBuild); } } \ No newline at end of file diff --git a/test/unitTests/latestVersionSelector.test.ts b/test/unitTests/latestVersionSelector.test.ts new file mode 100644 index 0000000000..a2d2f04b56 --- /dev/null +++ b/test/unitTests/latestVersionSelector.test.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import { should } from 'chai'; +import { GetLatestInstalledExperimentalVersion } from '../../src/omnisharp/latestVersionSelector'; + +const tmp = require('tmp'); + +suite("Experimental Omnisharp - Latest Version", () => { + suiteSetup(() => should()); + + test('Returns latest version', () => { + let versions: string[] = ["1.28.0", "1.27.0", "1.26.0"]; + let latestVersion = GetLatestVersion(versions); + latestVersion.should.equal("1.28.0"); + }); + + test('Ignores unparseable strings', () => { + let versions: string[] = ["1.28.0", "1.27.0", "1.26.0", "a.b.c"]; + let latestVersion = GetLatestVersion(versions); + latestVersion.should.equal("1.28.0"); + }); + + test('Returns pre-release versions if they are the latest', () => { + let versions: string[] = ["1.28.0", "1.27.0", "1.26.0", "1.29.0-beta1"]; + let latestVersion = GetLatestVersion(versions); + latestVersion.should.equal("1.29.0-beta1"); + }); + + test('Returns the latest pre-release version', () => { + let versions: string[] = ["1.28.0", "1.27.0", "1.29.0-beta2", "1.29.0-beta1"]; + let latestVersion = GetLatestVersion(versions); + latestVersion.should.equal("1.29.0-beta2"); + }); + + test('Returns the prod version over pre-release version', () => { + let versions: string[] = ["1.28.0", "1.27.0", "1.29.0", "1.29.0-beta1"]; + let latestVersion = GetLatestVersion(versions); + latestVersion.should.equal("1.29.0"); + }); + + test('Returns undefined if no valid version exists', () => { + let versions: string[] = ["a.b.c"]; + let latestVersion = GetLatestVersion(versions); + latestVersion.should.equal(""); + }); + + test('Returns empty if folder is empty', () => { + let versions: string[] = []; + let latestVersion = GetLatestVersion(versions); + latestVersion.should.equal(""); + }); + + test('Returns empty if experimental folder doesnot exist', () => { + let latestVersion = GetLatestInstalledExperimentalVersion(""); + latestVersion.should.equal(""); + }); +}); + +function GetLatestVersion(versions: string[]): string { + let tmpDir = tmp.dirSync(); + let dirPath = tmpDir.name; + AddVersionsToDirectory(dirPath, versions); + let latestVersion = GetLatestInstalledExperimentalVersion(dirPath); + CleanUpDirectory(dirPath); + tmpDir.removeCallback(); + return latestVersion; +} + +function AddVersionsToDirectory(dirPath: string, versions: string[]) { + for (let version of versions) { + fs.mkdirSync(`${dirPath}/${version}`); + } +} + +function CleanUpDirectory(dirPath: string) { + let installedVersions = fs.readdirSync(dirPath); + for (let version of installedVersions) { + fs.rmdirSync(`${dirPath}/${version}`); + } +}