"fix(viper): vpjEditor 与命令注册加容错,避免调试双加载时重复注册导致扩展激活失败
Validation / Typecheck (push) Has been cancelled
Validation / Style (push) Has been cancelled
Validation / Test macos-latest (push) Has been cancelled
Validation / Test ubuntu-latest (push) Has been cancelled
Validation / Test windows-latest (push) Has been cancelled
Validation / Build (push) Has been cancelled
Validation / Required (push) Has been cancelled

fix(lsp): 源文件位于执行根之外时仍从同目录解析绝对导入,修复 import lib 无法解析及白色高亮"
This commit is contained in:
TermiNexus committed 2026-08-08 02:16:39 +08:00
1 parent 11e83eb2be
commit 417bc8a2be
4 files changed
+60 -14

No files matched your search

+2 -2
View File
@@ -51,7 +51,7 @@
"preLaunchTask": "npm: build:extension:dev",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/packages/vscode-Vipeye",
"--extensionDevelopmentPath=${workspaceFolder}/packages/viper",
//"--extensionDevelopmentPath=${workspaceFolder}/packages/viper",
// The published extension is named "ms-Vipeye.Vipeye", but in debug mode it's "ms-Vipeye.vscode-Vipeye"
// to allow the extension code to participate in the lerna monorepo. Make sure that the published extension
// isn't enabled, otherwise both are loaded and conflict.
@@ -71,7 +71,7 @@
"preLaunchTask": "Watch extension",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/packages/vscode-Vipeye",
"--extensionDevelopmentPath=${workspaceFolder}/packages/viper",
// "--extensionDevelopmentPath=${workspaceFolder}/packages/viper",
// The published extension is named "ms-Vipeye.Vipeye", but in debug mode it's "ms-Vipeye.vscode-Vipeye"
// to allow the extension code to participate in the lerna monorepo. Make sure that the published extension
// isn't enabled, otherwise both are loaded and conflict.
+22 -6
View File
@@ -52,28 +52,28 @@ export async function activate(context: ExtensionContext): Promise<ViperApiImpl>
// 命令。
context.subscriptions.push(
commands.registerCommand('viper.createProject', async () => {
RegisterCommandSafe(context, 'viper.createProject', async () => {
await CreateProjectCommand();
await projects.DiscoverProjects();
projectView.Refresh();
}),
commands.registerCommand('viper.selectToolchain', async () => {
RegisterCommandSafe(context, 'viper.selectToolchain', async () => {
await toolchain.SelectToolchain();
}),
commands.registerCommand('viper.showProjectInfo', () => {
RegisterCommandSafe(context, 'viper.showProjectInfo', () => {
ShowProjectInfoCommand(projects);
}),
commands.registerCommand('viper.openSettings', () => {
RegisterCommandSafe(context, 'viper.openSettings', () => {
commands.executeCommand('workbench.action.openSettings', '@ext:TermiNexus.vipeye');
}),
commands.registerCommand('viper.refreshView', async () => {
RegisterCommandSafe(context, 'viper.refreshView', async () => {
await projects.DiscoverProjects();
toolchain.Refresh();
projectView.Refresh();
toolchainView.Refresh();
settingsView.Refresh();
}),
commands.registerCommand('viper.openVpjAsJson', async (uri?: import('vscode').Uri) => {
RegisterCommandSafe(context, 'viper.openVpjAsJson', async (uri?: import('vscode').Uri) => {
const target = uri ?? window.activeTextEditor?.document.uri;
if (!target) {
window.showWarningMessage('No .vpj file selected.');
@@ -102,6 +102,22 @@ export function deactivate(): void {
// 清理由 context.subscriptions 自动处理。
}
/** 安全注册命令:vscode-vipeye 单一合并了核心代码,若与独立的 viper 核心扩展
* 同时激活,命令可能已被先注册的扩展注册(command 'xxx' already exists)。
* 此处捕获重复注册错误并跳过,避免整个扩展激活失败;命令由先注册方提供。 */
function RegisterCommandSafe(
context: ExtensionContext,
commandId: string,
handler: (...args: any[]) => any
): import('vscode').Disposable {
try {
return commands.registerCommand(commandId, handler);
} catch (e) {
console.warn(`[viper] command '${commandId}' already registered by another extension, skipping.`);
return { dispose() {} };
}
}
/** 创建项目命令:在当前 workspace 根目录生成 .vpj 模板。 */
async function CreateProjectCommand(): Promise<void> {
if (!workspace.workspaceFolders || workspace.workspaceFolders.length === 0) {
+12 -4
View File
@@ -18,10 +18,18 @@ export class VpjEditorProvider implements vscode.CustomTextEditorProvider {
public static Register(context: vscode.ExtensionContext): vscode.Disposable {
const provider = new VpjEditorProvider(context);
return vscode.window.registerCustomEditorProvider(VpjEditorProvider.ViewType, provider, {
webviewOptions: { retainContextWhenHidden: true },
supportsMultipleEditorsPerDocument: false,
});
try {
return vscode.window.registerCustomEditorProvider(VpjEditorProvider.ViewType, provider, {
webviewOptions: { retainContextWhenHidden: true },
supportsMultipleEditorsPerDocument: false,
});
} catch (e) {
// viper 核心扩展与合并了核心代码的 vscode-vipeye 同时激活时,
// 后激活者会因 viewType 已注册而抛错,导致整个扩展激活失败。
// 此处跳过重复注册,编辑器由先注册的扩展提供,功能等价。
console.warn(`[viper] ${VpjEditorProvider.ViewType} already registered by another extension, skipping.`);
return { dispose() {} };
}
}
constructor(private readonly context: vscode.ExtensionContext) {}
@@ -559,6 +559,8 @@ export class ImportResolver {
// and so on, until the import root is reached.
const origin = sourceFileUri.getDirectory();
const importLogger = this._configOptions.verboseOutput ? new ImportLogger() : undefined;
const result = this.cachedParentImportResults.getImportResult(origin, importName, importResult);
if (result) {
// Already ran the parent directory resolution for this import name on this location.
@@ -568,11 +570,31 @@ export class ImportResolver {
// Check whether the given file is in the parent directory import resolution cache.
const root = getParentImportResolutionRoot(sourceFileUri, execEnv.root);
if (!this.cachedParentImportResults.checkValidPath(this.fileSystem, sourceFileUri, root)) {
// Viper 扩展:源文件不在执行根下(例如 LSP 工作区根被 .vpj 子项目收窄,
// main.py 位于子项目目录之外)时,Pyright 原逻辑直接放弃父目录回退。
// 这里仍从源文件自身目录解析一次绝对导入(Python 语义:脚本所在目录
// 在 sys.path 上),支持 main.py + 同目录包(如 App/lib)的导入。
// 不做向上遍历,避免误解析到执行根外的父目录包。
if (IsViperDialect(this._configOptions.dialect)) {
const originResult = this.resolveAbsoluteImport(
sourceFileUri,
origin,
execEnv,
moduleDescriptor,
importName,
importLogger,
/* allowPartial */ undefined,
/* allowNativeLib */ undefined,
/* useStubPackage */ false,
/* allowPyi */ true
);
if (originResult?.isImportFound) {
return this.filterImplicitImports(originResult, moduleDescriptor.importedSymbols);
}
}
return importResult;
}
const importLogger = this._configOptions.verboseOutput ? new ImportLogger() : undefined;
const importPath: ImportPath = { importPath: undefined };
// Going up the given folder one by one until we can resolve the import.