Per-project launch options, publish/test tasks, explorer badge, keybindings
- dotnetSolution.launch.projects: args, env, cwd, console and profile per project name, layered over the global settings and Properties/launchSettings.json. Applied to the debug button and, through resolveDebugConfiguration, to the launch.json entry too. - publish and test task targets with the same configuration/platform. - Problems panel opens when a task fails. - The startup csproj and its folder are marked in the explorer. - F5 / Ctrl+F5 / Ctrl+Shift+B map to debug/run/build until a launch.json entry exists; Ctrl+Alt+C and Ctrl+Alt+P open the pickers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169iPWwKHZoBTNN9qwXiwqk
This commit is contained in:
+69
-4
@@ -3,7 +3,8 @@ import * as path from 'path';
|
||||
import { buildLaunchConfiguration, LAUNCH_NAME, launchJsonEntry, SolutionDebugConfigurationProvider } from './launch';
|
||||
import { SolutionModel, StartupProject } from './model';
|
||||
import { configurationKey, projectConfigurationFor, SolutionConfiguration } from './sln';
|
||||
import { createTask, runTask, SolutionTaskProvider, TASK_TYPE, taskLabel, BuildTarget } from './tasks';
|
||||
import { ALL_TARGETS, createTask, runTask, SolutionTaskProvider, TASK_TYPE, taskLabel, BuildTarget } from './tasks';
|
||||
import { launchOptionsFor } from './launch';
|
||||
import { StatusBar } from './status';
|
||||
|
||||
let model: SolutionModel;
|
||||
@@ -109,8 +110,12 @@ async function build(target: BuildTarget): Promise<number> {
|
||||
try {
|
||||
const code = await runTask(task);
|
||||
if (code !== 0) {
|
||||
if (settings().get<boolean>('showProblemsOnFailure', true)) {
|
||||
// The $msCompile matcher has already filled the Problems panel by now.
|
||||
void vscode.commands.executeCommand('workbench.actions.view.problems');
|
||||
}
|
||||
void vscode.window.showErrorMessage(
|
||||
`${task.name} failed (exit code ${code}). See the terminal or the Problems panel.`);
|
||||
`${task.name} failed (exit code ${code}). See the Problems panel or the terminal.`);
|
||||
}
|
||||
return code;
|
||||
} finally {
|
||||
@@ -167,8 +172,7 @@ async function generateLaunchConfig(): Promise<void> {
|
||||
const tasks = vscode.workspace.getConfiguration('tasks', folder.uri);
|
||||
const existing = (tasks.get<Record<string, unknown>[]>('tasks') ?? [])
|
||||
.filter(entry => entry.type !== TASK_TYPE);
|
||||
const targets: BuildTarget[] = ['build', 'rebuild', 'clean'];
|
||||
for (const target of targets) {
|
||||
for (const target of ALL_TARGETS) {
|
||||
existing.push({
|
||||
label: taskLabel(target),
|
||||
type: TASK_TYPE,
|
||||
@@ -253,6 +257,46 @@ async function syncWorkspaceProperties(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- explorer badge ---------------------------------------------------------------
|
||||
|
||||
/** Marks the startup project file, and the folder holding it, in the explorer. */
|
||||
class StartupProjectDecorations implements vscode.FileDecorationProvider {
|
||||
private readonly changed = new vscode.EventEmitter<vscode.Uri | vscode.Uri[] | undefined>();
|
||||
readonly onDidChangeFileDecorations = this.changed.event;
|
||||
private current: string | undefined;
|
||||
|
||||
update(projectPath: string | undefined): void {
|
||||
this.current = projectPath ? path.normalize(projectPath).toLowerCase() : undefined;
|
||||
this.changed.fire(undefined);
|
||||
}
|
||||
|
||||
provideFileDecoration(uri: vscode.Uri): vscode.FileDecoration | undefined {
|
||||
if (!this.current || uri.scheme !== 'file') {
|
||||
return undefined;
|
||||
}
|
||||
const fsPath = path.normalize(uri.fsPath).toLowerCase();
|
||||
const isProject = fsPath === this.current;
|
||||
const isFolder = fsPath === path.dirname(this.current);
|
||||
if (!isProject && !isFolder) {
|
||||
return undefined;
|
||||
}
|
||||
const decoration = new vscode.FileDecoration('▶', 'Startup project (.NET Solution)',
|
||||
new vscode.ThemeColor('debugIcon.startForeground'));
|
||||
decoration.propagate = false;
|
||||
return decoration;
|
||||
}
|
||||
}
|
||||
|
||||
/** `when` clause contexts for the keybindings. */
|
||||
function updateContexts(): void {
|
||||
const active = model.active;
|
||||
void vscode.commands.executeCommand('setContext', 'dotnetSolution.active', active !== undefined);
|
||||
const folder = active ? vscode.workspace.getWorkspaceFolder(vscode.Uri.file(active.solution.fsPath)) : undefined;
|
||||
const configurations = vscode.workspace.getConfiguration('launch', folder?.uri).get<{ name?: string }[]>('configurations') ?? [];
|
||||
void vscode.commands.executeCommand('setContext', 'dotnetSolution.hasLaunchEntry',
|
||||
configurations.some(entry => entry.name === LAUNCH_NAME));
|
||||
}
|
||||
|
||||
// ---- activation -------------------------------------------------------------------
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
@@ -279,6 +323,8 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
command('dotnetSolution.build', () => build('build')),
|
||||
command('dotnetSolution.rebuild', () => build('rebuild')),
|
||||
command('dotnetSolution.clean', () => build('clean')),
|
||||
command('dotnetSolution.publish', () => build('publish')),
|
||||
command('dotnetSolution.test', () => build('test')),
|
||||
command('dotnetSolution.debug', () => launch(false)),
|
||||
command('dotnetSolution.run', () => launch(true)),
|
||||
command('dotnetSolution.generateLaunchConfig', generateLaunchConfig),
|
||||
@@ -301,6 +347,11 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
}),
|
||||
command('dotnetSolution.activeTargetPath', async () => (await model.resolveTarget()).targetPath),
|
||||
command('dotnetSolution.activeTargetDir', async () => (await model.resolveTarget()).targetDir.replace(/[\\/]+$/, '')),
|
||||
command('dotnetSolution.activeCwd', async () => {
|
||||
const info = active()?.project.info;
|
||||
const own = info ? launchOptionsFor(info.name, info.fsPath, path.dirname(info.fsPath)).cwd : undefined;
|
||||
return own ?? (await model.resolveTarget()).targetDir.replace(/[\\/]+$/, '');
|
||||
}),
|
||||
command('dotnetSolution.activeProjectPath', () => active()?.project.info.fsPath),
|
||||
command('dotnetSolution.activeProjectName', () => active()?.project.info.name),
|
||||
command('dotnetSolution.activeSolutionPath', () => active()?.solution.fsPath),
|
||||
@@ -320,6 +371,20 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
}),
|
||||
);
|
||||
|
||||
const decorations = new StartupProjectDecorations();
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerFileDecorationProvider(decorations),
|
||||
model.onDidChange(() => {
|
||||
decorations.update(model.startupProject?.info.fsPath);
|
||||
updateContexts();
|
||||
}),
|
||||
vscode.workspace.onDidChangeConfiguration(event => {
|
||||
if (event.affectsConfiguration('launch')) {
|
||||
updateContexts();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
connectDotRush(context);
|
||||
void model.reload().then(() => offerLaunchJson(context));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user