Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show C# project files in the project selector #4644

Merged
merged 6 commits into from
Jul 14, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions src/features/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { OmniSharpServer } from '../omnisharp/server';
import * as serverUtils from '../omnisharp/utils';
import { findLaunchTargets } from '../omnisharp/launcher';
import { findLaunchTargets, LaunchTarget } from '../omnisharp/launcher';
import * as cp from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
Expand Down Expand Up @@ -92,14 +92,18 @@ async function pickProjectAndStart(server: OmniSharpServer, optionProvider: Opti
}
}

return vscode.window.showQuickPick(targets, {
matchOnDescription: true,
placeHolder: `Select 1 of ${targets.length} projects`
}).then(async launchTarget => {
if (launchTarget) {
return server.restart(launchTarget);
}
});
return showProjectSelector(server, targets);
});
}

export async function showProjectSelector(server: OmniSharpServer, targets: LaunchTarget[]): Promise<void> {
return vscode.window.showQuickPick(targets, {
matchOnDescription: true,
placeHolder: `Select 1 of ${targets.length} projects`
}).then(async launchTarget => {
if (launchTarget) {
return server.restart(launchTarget);
}
});
}

Expand Down
23 changes: 19 additions & 4 deletions src/observers/ProjectStatusBarObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { basename } from 'path';
import { basename, join } from 'path';
import { BaseEvent, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents";
import { BaseStatusBarItemObserver } from './BaseStatusBarItemObserver';
import { EventType } from '../omnisharp/EventType';
Expand All @@ -25,9 +25,24 @@ export class ProjectStatusBarObserver extends BaseStatusBarItemObserver {

private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) {
let label: string;
let info = event.info;
if (info.MsBuild && info.MsBuild.SolutionPath) {
label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath);
let msbuild = event.info.MsBuild;
if (msbuild && msbuild.SolutionPath) {
if (msbuild.SolutionPath.endsWith(".sln")) {
label = basename(msbuild.SolutionPath);
}
else {
// a project file was open, determine which project
for (const project of msbuild.Projects) {
// Get the project name.
label = basename(project.Path);

// The solution path is the folder containing the open project. Combine it with the
// project name and see if it matches the project's path.
if (join(msbuild.SolutionPath, label) === project.Path) {
break;
}
}
}
this.SetAndShowStatusBar('$(file-directory) ' + label, 'o.pickProjectAndStart');
}
else {
Expand Down
102 changes: 53 additions & 49 deletions src/omnisharp/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { IMonoResolver } from '../constants/IMonoResolver';

export enum LaunchTargetKind {
Solution,
Project,
ProjectJson,
Folder,
Csx,
Expand Down Expand Up @@ -115,82 +116,81 @@ export function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[
}
}

let targets: LaunchTarget[] = [];
return resourcesAndFolderMapToLaunchTargets(resources, vscode.workspace.workspaceFolders.concat(), workspaceFolderToUriMap);
}

workspaceFolderToUriMap.forEach((resources, folderIndex) => {
let hasCsProjFiles = false,
hasSlnFile = false,
hasProjectJson = false,
hasProjectJsonAtRoot = false,
hasCSX = false,
hasCake = false,
hasCs = false;
export function resourcesAndFolderMapToLaunchTargets(resources: vscode.Uri[], workspaceFolders: vscode.WorkspaceFolder[], workspaceFolderToUriMap: Map<number, vscode.Uri[]>): LaunchTarget[] {
let solutionTargets: LaunchTarget[] = [];
let projectJsonTargets: LaunchTarget[] = [];
let projectTargets: LaunchTarget[] = [];
let otherTargets: LaunchTarget[] = [];

hasCsProjFiles = resources.some(isCSharpProject);
workspaceFolderToUriMap.forEach((resources, folderIndex) => {
let hasProjectJsonAtRoot = false;
let hasCSX = false;
let hasCake = false;
let hasCs = false;

let folder = vscode.workspace.workspaceFolders[folderIndex];
let folder = workspaceFolders[folderIndex];
let folderPath = folder.uri.fsPath;

resources.forEach(resource => {
// Add .sln and .slnf files if there are .csproj files
if (hasCsProjFiles && isSolution(resource)) {
hasSlnFile = true;
targets.push({
// Add .sln and .slnf files
if (isSolution(resource)) {
const dirname = path.dirname(resource.fsPath);
solutionTargets.push({
label: path.basename(resource.fsPath),
description: vscode.workspace.asRelativePath(path.dirname(resource.fsPath)),
description: vscode.workspace.asRelativePath(dirname),
target: resource.fsPath,
directory: path.dirname(resource.fsPath),
kind: LaunchTargetKind.Solution
});
}

// Add project.json files
if (isProjectJson(resource)) {
else if (isProjectJson(resource)) {
const dirname = path.dirname(resource.fsPath);
hasProjectJson = true;
hasProjectJsonAtRoot = hasProjectJsonAtRoot || dirname === folderPath;

targets.push({
projectJsonTargets.push({
label: path.basename(resource.fsPath),
description: vscode.workspace.asRelativePath(path.dirname(resource.fsPath)),
description: vscode.workspace.asRelativePath(dirname),
target: dirname,
directory: dirname,
kind: LaunchTargetKind.ProjectJson
});
}

// Discover if there is any CSX file
if (!hasCSX && isCsx(resource)) {
hasCSX = true;
// Add .csproj files
else if (isCSharpProject(resource)) {
const dirname = path.dirname(resource.fsPath);
// OmniSharp doesn't support opening a project directly, however, it will open a project if
// we pass a folder path which contains a single .csproj. This is similar to how project.json
// is supported.
projectTargets.push({
label: path.basename(resource.fsPath),
description: vscode.workspace.asRelativePath(dirname),
target: dirname,
directory: dirname,
kind: LaunchTargetKind.Project
});
}
else {
// Discover if there is any CSX file
hasCSX ||= isCsx(resource);

// Discover if there is any Cake file
if (!hasCake && isCake(resource)) {
hasCake = true;
}
// Discover if there is any Cake file
hasCake ||= isCake(resource);

//Discover if there is any cs file
if (!hasCs && isCs(resource)) {
hasCs = true;
//Discover if there is any cs file
hasCs ||= isCs(resource);
}
});

// Add the root folder under the following circumstances:
// * If there are .csproj files, but no .sln or .slnf file, and none in the root.
// * If there are project.json files, but none in the root.
if ((hasCsProjFiles && !hasSlnFile) || (hasProjectJson && !hasProjectJsonAtRoot)) {
targets.push({
label: path.basename(folderPath),
description: '',
target: folderPath,
directory: folderPath,
kind: LaunchTargetKind.Folder
});
}
const hasCsProjFiles = projectTargets.length > 0;
const hasSlnFile = solutionTargets.length > 0;
const hasProjectJson = projectJsonTargets.length > 0;

// if we noticed any CSX file(s), add a single CSX-specific target pointing at the root folder
if (hasCSX) {
targets.push({
otherTargets.push({
label: "CSX",
description: path.basename(folderPath),
target: folderPath,
Expand All @@ -201,7 +201,7 @@ export function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[

// if we noticed any Cake file(s), add a single Cake-specific target pointing at the root folder
if (hasCake) {
targets.push({
otherTargets.push({
label: "Cake",
description: path.basename(folderPath),
target: folderPath,
Expand All @@ -211,7 +211,7 @@ export function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[
}

if (hasCs && !hasSlnFile && !hasCsProjFiles && !hasProjectJson && !hasProjectJsonAtRoot) {
targets.push({
otherTargets.push({
label: path.basename(folderPath),
description: '',
target: folderPath,
Expand All @@ -221,7 +221,11 @@ export function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[
}
});

return targets.sort((a, b) => a.directory.localeCompare(b.directory));
solutionTargets = solutionTargets.sort((a, b) => a.directory.localeCompare(b.directory));
projectJsonTargets = projectJsonTargets.sort((a, b) => a.directory.localeCompare(b.directory));
projectTargets = projectTargets.sort((a, b) => a.directory.localeCompare(b.directory));

return otherTargets.concat(solutionTargets).concat(projectJsonTargets).concat(projectTargets);
}

function isCSharpProject(resource: vscode.Uri): boolean {
Expand Down
26 changes: 12 additions & 14 deletions src/omnisharp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import CompositeDisposable from '../CompositeDisposable';
import Disposable from '../Disposable';
import OptionProvider from '../observers/OptionProvider';
import { IMonoResolver } from '../constants/IMonoResolver';
import { showProjectSelector } from '../features/commands';
import { removeBOMFromBuffer, removeBOMFromString } from '../utils/removeBOM';

enum ServerState {
Expand Down Expand Up @@ -537,10 +538,13 @@ export class OmniSharpServer {
return this.autoStart(preferredPath);
});
}

const defaultLaunchSolutionConfigValue = this.optionProvider.GetLatestOptions().defaultLaunchSolution;
else if (launchTargets.length === 1) {
// If there's only one target, just start
return this.restart(launchTargets[0]);
}

// First, try to launch against something that matches the user's preferred target
const defaultLaunchSolutionConfigValue = this.optionProvider.GetLatestOptions().defaultLaunchSolution;
const defaultLaunchSolutionTarget = launchTargets.find((a) => (path.basename(a.target) === defaultLaunchSolutionConfigValue));
if (defaultLaunchSolutionTarget) {
return this.restart(defaultLaunchSolutionTarget);
Expand All @@ -549,21 +553,15 @@ export class OmniSharpServer {
// If there's more than one launch target, we start the server if one of the targets
// matches the preferred path. Otherwise, we fire the "MultipleLaunchTargets" event,
// which is handled in status.ts to display the launch target selector.
if (launchTargets.length > 1 && preferredPath) {

for (let launchTarget of launchTargets) {
if (launchTarget.target === preferredPath) {
// start preferred path
return this.restart(launchTarget);
}
if (preferredPath) {
const preferredLaunchTarget = launchTargets.find((a) => a.target === preferredPath);
if (preferredLaunchTarget) {
return this.restart(preferredLaunchTarget);
}

this._fireEvent(Events.MultipleLaunchTargets, launchTargets);
return Promise.reject<void>(undefined);
}

// If there's only one target, just start
return this.restart(launchTargets[0]);
this._fireEvent(Events.MultipleLaunchTargets, launchTargets);
return showProjectSelector(this, launchTargets);
});
}

Expand Down
34 changes: 21 additions & 13 deletions test/integrationTests/launcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,10 @@

import * as vscode from 'vscode';
import { assert } from "chai";
import { resourcesToLaunchTargets, vsls, vslsTarget } from "../../src/omnisharp/launcher";
import { isSlnWithGenerator } from './integrationHelpers';

const chai = require('chai');
chai.use(require('chai-arrays'));
chai.use(require('chai-fs'));
import { LaunchTargetKind, resourcesAndFolderMapToLaunchTargets, resourcesToLaunchTargets, vsls, vslsTarget } from "../../src/omnisharp/launcher";

suite(`launcher:`, () => {

suiteSetup(async function () {
if (isSlnWithGenerator(vscode.workspace)) {
this.skip();
}
});
const workspaceFolders: vscode.WorkspaceFolder[] = [{ uri: vscode.Uri.parse('/'), name: "root", index: 0 }];

test(`Returns the LiveShare launch target when processing vsls resources`, () => {
const testResources: vscode.Uri[] = [
Expand All @@ -39,10 +29,28 @@ suite(`launcher:`, () => {
vscode.Uri.parse(`/test/test.csproj`),
vscode.Uri.parse(`/test/Program.cs`),
];
const folderMap = new Map<number, vscode.Uri[]>([[0, testResources]]);

const launchTargets = resourcesToLaunchTargets(testResources);
const launchTargets = resourcesAndFolderMapToLaunchTargets(testResources, workspaceFolders, folderMap);

const liveShareTarget = launchTargets.find(target => target === vslsTarget);
assert.notExists(liveShareTarget, "Launch targets contained the Visual Studio Live Share target.");
});

test(`Returns a Solution and Project target`, () => {
const testResources: vscode.Uri[] = [
vscode.Uri.parse(`/test.sln`),
vscode.Uri.parse(`/test/test.csproj`),
vscode.Uri.parse(`/test/Program.cs`),
];
const folderMap = new Map<number, vscode.Uri[]>([[0, testResources]]);

const launchTargets = resourcesAndFolderMapToLaunchTargets(testResources, workspaceFolders, folderMap);

const solutionTarget = launchTargets.find(target => target.kind === LaunchTargetKind.Solution && target.label === "test.sln");
assert.exists(solutionTarget, "Launch targets did not include `/test.sln`");

const projectTarget = launchTargets.find(target => target.kind === LaunchTargetKind.Project && target.label === "test.csproj");
assert.exists(projectTarget, "Launch targets did not include `/test/test.csproj`");
});
});