feat(viper): 选择列表加「选择编译器…」,关闭 VET 后清单与选中仍可用
- 选择列表首项为「选择编译器…」(文件选择对话框 → VET check → 并入清单并切换),其后一条分隔线才是 VET 搜到的环境 - 状态栏在 VET 关闭时显示 VET 已关闭,且点击仍打开选择列表(重启改走命令面板) - EnvironmentManager 新增 AddEnvironment:check 通过的环境无需等下一轮快照即可选中 - 抽出 PickAndCheckExecutable,去掉 check 命令与该入口的重复实现
This commit is contained in:
1 parent
ad3fd9810c
commit
95a0dbf0ad
3 files changed
+151
-61
No files matched your search
@@ -6,7 +6,15 @@
|
||||
* 并把「选中哪个环境」持久化到扩展状态(globalState)——刻意不写回 .vpj,保持项目文件干净。
|
||||
*/
|
||||
|
||||
import { Disposable, Event, EventEmitter, Memento, QuickPickItem, window, commands } from 'vscode';
|
||||
import {
|
||||
Disposable,
|
||||
Event,
|
||||
EventEmitter,
|
||||
Memento,
|
||||
QuickPickItem,
|
||||
QuickPickItemKind,
|
||||
window,
|
||||
} from 'vscode';
|
||||
|
||||
import { ViperEnvironment } from './types';
|
||||
|
||||
@@ -20,11 +28,18 @@ export interface EnvironmentChange {
|
||||
Removed: ViperEnvironment[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 「手动指定编译器」的处理(由扩展入口注入:弹输入框取路径 + 走 VET 的 check 通道)。
|
||||
* 返回被选中的环境表示已处理完;返回 undefined 表示用户取消或校验未通过。
|
||||
*/
|
||||
export type ManualCompilerPicker = () => Promise<ViperEnvironment | undefined>;
|
||||
|
||||
export class EnvironmentManager implements Disposable {
|
||||
private static readonly SelectedIdKey = 'viper.environmentTool.selectedId';
|
||||
|
||||
private environments: ViperEnvironment[] = [];
|
||||
private selectedId: string | undefined;
|
||||
private manualCompilerPicker: ManualCompilerPicker | undefined;
|
||||
|
||||
private readonly _onDidChangeEnvironments = new EventEmitter<EnvironmentChange>();
|
||||
readonly OnDidChangeEnvironments: Event<EnvironmentChange> = this._onDidChangeEnvironments.event;
|
||||
@@ -92,7 +107,8 @@ export class EnvironmentManager implements Disposable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择环境。
|
||||
* 选择编译器。列表结构:首项是「选择编译器…」(手动输入 exe 路径),随后一条分隔线,
|
||||
* 再之后才是 VET 搜到的环境。VET 关闭时这个列表照样可用(已发现的环境仍能选用)。
|
||||
* @param environmentId 传入则直接选中该 id;不传则弹 QuickPick 让用户选。
|
||||
*/
|
||||
async SelectEnvironment(environmentId?: string): Promise<ViperEnvironment | undefined> {
|
||||
@@ -100,31 +116,59 @@ export class EnvironmentManager implements Disposable {
|
||||
return this.ApplySelection(environmentId);
|
||||
}
|
||||
|
||||
const all = this.environments;
|
||||
if (all.length === 0) {
|
||||
const picked = await window.showWarningMessage('VET 还没有发现任何 Viper 编译器。', '重启 VET 扫描');
|
||||
if (picked === '重启 VET 扫描') {
|
||||
void commands.executeCommand('viper.restartEnvironmentTool');
|
||||
}
|
||||
return undefined;
|
||||
const items: CompilerPickItem[] = [
|
||||
{
|
||||
label: '$(file) 选择编译器…',
|
||||
description: '手动指定 exe 路径',
|
||||
Manual: true,
|
||||
},
|
||||
];
|
||||
if (this.environments.length > 0) {
|
||||
items.push({ label: '', kind: QuickPickItemKind.Separator });
|
||||
items.push(
|
||||
...this.environments.map<EnvironmentQuickPickItem>((environment) => ({
|
||||
label: environment.DisplayName,
|
||||
description: `${environment.Version} · ${environment.Arch} · ${environment.Source}`,
|
||||
detail: environment.Executable,
|
||||
picked: environment.Id === this.selectedId,
|
||||
Environment: environment,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
const items = all.map<EnvironmentQuickPickItem>((environment) => ({
|
||||
label: environment.DisplayName,
|
||||
description: `${environment.Version} · ${environment.Arch} · ${environment.Source}`,
|
||||
detail: environment.Executable,
|
||||
picked: environment.Id === this.selectedId,
|
||||
Environment: environment,
|
||||
}));
|
||||
|
||||
const picked = await window.showQuickPick(items, {
|
||||
title: '选择 Viper 环境',
|
||||
placeHolder: '选择要使用的 Viper 编译器(仅保存在扩展状态,不写入 .vpj)',
|
||||
title: '选择编译器',
|
||||
placeHolder: '选择要为当前工作区使用的 Viper 编译器',
|
||||
matchOnDescription: true,
|
||||
matchOnDetail: true,
|
||||
});
|
||||
if (!picked) {
|
||||
return undefined;
|
||||
}
|
||||
if ('Manual' in picked) {
|
||||
return this.manualCompilerPicker ? await this.manualCompilerPicker() : undefined;
|
||||
}
|
||||
if ('Environment' in picked) {
|
||||
return this.ApplySelection(picked.Environment.Id);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return picked ? this.ApplySelection(picked.Environment.Id) : undefined;
|
||||
/** 注入「手动指定编译器」的处理(扩展入口提供:输入框取路径 + VET check)。 */
|
||||
SetManualCompilerPicker(picker: ManualCompilerPicker): void {
|
||||
this.manualCompilerPicker = picker;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把一个刚通过 VET check 的环境并入清单 —— 下一轮全量快照(≤ scanIntervalMs)到达前
|
||||
* 就能立刻选中它;快照到达时因为已在表中,diff 不会重复算作新增。
|
||||
*/
|
||||
AddEnvironment(environment: ViperEnvironment): void {
|
||||
const next = [...this.environments.filter((item) => item.Id !== environment.Id), environment].sort(
|
||||
CompareEnvironments
|
||||
);
|
||||
this.environments = next;
|
||||
this._onDidChangeEnvironments.fire({ Environments: next, Added: [environment], Removed: [] });
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -145,10 +189,18 @@ export class EnvironmentManager implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
/** 「选择编译器…」项:点击后走手动输入路径。 */
|
||||
interface ManualPickItem extends QuickPickItem {
|
||||
Manual: true;
|
||||
}
|
||||
|
||||
interface EnvironmentQuickPickItem extends QuickPickItem {
|
||||
Environment: ViperEnvironment;
|
||||
}
|
||||
|
||||
/** 选择列表上的项:手动项 / VET 搜到的环境 / 分隔线。 */
|
||||
type CompilerPickItem = ManualPickItem | EnvironmentQuickPickItem | QuickPickItem;
|
||||
|
||||
/** 排序:先按来源分组,再按展示名;保证每轮快照顺序稳定(避免视图抖动)。 */
|
||||
function CompareEnvironments(left: ViperEnvironment, right: ViperEnvironment): number {
|
||||
if (left.Source !== right.Source) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { EnvironmentManager } from './environmentManager';
|
||||
import { EnvironmentTool } from './environmentTool';
|
||||
import { ProjectManager } from './projectManager';
|
||||
import { ToolchainManager } from './toolchainManager';
|
||||
import { ViperCheckResult } from './types';
|
||||
import { ViperCheckResult, ViperEnvironment } from './types';
|
||||
import { ViperViewProvider } from './viperViewProvider';
|
||||
import { EnvironmentStatusBar } from './statusBar';
|
||||
import { ViperApiImpl } from './api';
|
||||
@@ -98,6 +98,9 @@ export async function activate(context: ExtensionContext): Promise<ViperApiImpl>
|
||||
})
|
||||
);
|
||||
|
||||
// 选择列表里的「选择编译器…」:手动输入路径 → VET check → 并入环境清单并切过去。
|
||||
environments.SetManualCompilerPicker(() => PickManualCompiler(environmentTool, environments));
|
||||
|
||||
// 命令。
|
||||
context.subscriptions.push(
|
||||
RegisterCommandSafe(context, 'viper.createProject', async () => {
|
||||
@@ -244,53 +247,85 @@ async function CreateProjectCommand(): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动指定一个 exe,经 VET 的 check 通道校验(PE/.PRVERSN + `--version` 前缀为 Viper)。
|
||||
* 校验通过后 VET 会把它入库并写回缓存文件,下一轮快照里就会出现在 Environments 视图。
|
||||
* 弹文件选择框挑一个 exe,并经 VET 的 check 通道校验。
|
||||
* 返回回执;用户取消时返回 undefined(校验失败会在这里给出错误提示)。
|
||||
*/
|
||||
async function CheckEnvironmentCommand(tool: EnvironmentTool): Promise<void> {
|
||||
async function PickAndCheckExecutable(tool: EnvironmentTool): Promise<ViperCheckResult | undefined> {
|
||||
const picked = await window.showOpenDialog({
|
||||
title: '选择要检查的 Viper 可执行文件',
|
||||
title: '选择 Viper 编译器',
|
||||
canSelectMany: false,
|
||||
openLabel: '检查',
|
||||
openLabel: '选择',
|
||||
filters: { 'Viper 可执行文件': ['exe'] },
|
||||
});
|
||||
const exePath = picked?.[0]?.fsPath;
|
||||
if (!exePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return window.withProgress(
|
||||
{ location: ProgressLocation.Notification, title: `VET 正在检查 ${path.basename(exePath)}…` },
|
||||
async () => {
|
||||
try {
|
||||
return await tool.CheckExecutable(exePath);
|
||||
} catch (e) {
|
||||
void window.showErrorMessage(`VET 检查失败:${e instanceof Error ? e.message : String(e)}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 「选择编译器…」:选一个 exe,经 VET check 通过后并入环境清单并立刻切换过去
|
||||
* (VET 会把该 exe 入库,后续轮次快照里也会带着它)。
|
||||
*/
|
||||
async function PickManualCompiler(
|
||||
tool: EnvironmentTool,
|
||||
environments: EnvironmentManager
|
||||
): Promise<ViperEnvironment | undefined> {
|
||||
const result = await PickAndCheckExecutable(tool);
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const environment = result.Environment;
|
||||
if (result.Ok && environment) {
|
||||
environments.AddEnvironment(environment);
|
||||
const selected = await environments.SelectEnvironment(environment.Id);
|
||||
void window.showInformationMessage(`已切换到 ${environment.DisplayName}(${environment.Arch})`);
|
||||
return selected;
|
||||
}
|
||||
|
||||
void window.showErrorMessage(`VET 检查未通过:${DescribeCheckMessage(result.Message)}`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** 手动指定一个 exe 做检查(只检查、不切换),结果通过信息消息回报。 */
|
||||
async function CheckEnvironmentCommand(tool: EnvironmentTool): Promise<void> {
|
||||
const result = await PickAndCheckExecutable(tool);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
await window.withProgress(
|
||||
{ location: ProgressLocation.Notification, title: `VET 正在检查 ${path.basename(exePath)}…` },
|
||||
async () => {
|
||||
let result: ViperCheckResult;
|
||||
try {
|
||||
result = await tool.CheckExecutable(exePath);
|
||||
} catch (e) {
|
||||
void window.showErrorMessage(`VET 检查失败:${e instanceof Error ? e.message : String(e)}`);
|
||||
return;
|
||||
}
|
||||
const environment = result.Environment;
|
||||
if (result.Ok && environment) {
|
||||
void window
|
||||
.showInformationMessage(
|
||||
`VET 检查通过:${environment.DisplayName}(${environment.Arch})`,
|
||||
'选择为当前环境',
|
||||
'显示详情'
|
||||
)
|
||||
.then((choice) => {
|
||||
if (choice === '选择为当前环境') {
|
||||
void commands.executeCommand('viper.selectEnvironment', environment.Id);
|
||||
} else if (choice === '显示详情') {
|
||||
void window.showInformationMessage(environment.Executable, { modal: true });
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const environment = result.Environment;
|
||||
if (result.Ok && environment) {
|
||||
void window
|
||||
.showInformationMessage(
|
||||
`VET 检查通过:${environment.DisplayName}(${environment.Arch})`,
|
||||
'选择为当前环境',
|
||||
'显示详情'
|
||||
)
|
||||
.then((choice) => {
|
||||
if (choice === '选择为当前环境') {
|
||||
void commands.executeCommand('viper.selectEnvironment', environment.Id);
|
||||
} else if (choice === '显示详情') {
|
||||
void window.showInformationMessage(environment.Executable, { modal: true });
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
void window.showErrorMessage(`VET 检查未通过:${DescribeCheckMessage(result.Message)}`);
|
||||
}
|
||||
);
|
||||
void window.showErrorMessage(`VET 检查未通过:${DescribeCheckMessage(result.Message)}`);
|
||||
}
|
||||
|
||||
/** 把 VET 回执里的 check_msg 固定原因串翻译成可读说明。 */
|
||||
|
||||
@@ -39,11 +39,12 @@ export class EnvironmentStatusBar implements Disposable {
|
||||
const count = this.environments.GetEnvironments().length;
|
||||
const stateText = DescribeState(state);
|
||||
|
||||
// 手动停止时点击直接把它拉起来(此时选择环境没有意义);其余情况点击打开环境选择器。
|
||||
this.item.command = state === 'stopped' ? 'viper.restartEnvironmentTool' : 'viper.selectEnvironment';
|
||||
// 点击状态栏始终打开选择列表:VET 关闭时也一样 —— 已发现的环境仍可选用,
|
||||
// 需要重启 VET 请走命令面板的「Restart VET」。
|
||||
this.item.command = 'viper.selectEnvironment';
|
||||
|
||||
if (state === 'stopped') {
|
||||
this.item.text = '$(debug-stop) Viper 环境: 已关闭';
|
||||
this.item.text = '$(debug-stop) VET 已关闭';
|
||||
} else if (selected) {
|
||||
this.item.text = `$(server-environment) Viper 环境: ${selected.DisplayName}`;
|
||||
} else if (count > 0) {
|
||||
@@ -60,7 +61,9 @@ export class EnvironmentStatusBar implements Disposable {
|
||||
selected ? `当前选中: ${selected.DisplayName}\n${selected.Executable}` : '当前选中: (无)',
|
||||
`VET: ${this.tool.GetExecutablePath()}`,
|
||||
'输出: Vipeye VET 输出通道',
|
||||
state === 'stopped' ? '点击重新启动 VET' : '点击选择环境',
|
||||
state === 'stopped'
|
||||
? '点击选择编译器(VET 已关闭,已发现的环境仍可选用;重启请用 Restart VET)'
|
||||
: '点击选择编译器',
|
||||
].join('\n');
|
||||
this.item.show();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user