From 417bc8a2be2df4f90180c79e3cf6278d32aa5d30 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 8 Aug 2026 02:16:39 +0800 Subject: [PATCH] =?UTF-8?q?"fix(viper):=20vpjEditor=20=E4=B8=8E=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E6=B3=A8=E5=86=8C=E5=8A=A0=E5=AE=B9=E9=94=99=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E8=B0=83=E8=AF=95=E5=8F=8C=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E6=97=B6=E9=87=8D=E5=A4=8D=E6=B3=A8=E5=86=8C=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E6=BF=80=E6=B4=BB=E5=A4=B1=E8=B4=A5=20fix(ls?= =?UTF-8?q?p):=20=E6=BA=90=E6=96=87=E4=BB=B6=E4=BD=8D=E4=BA=8E=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E6=A0=B9=E4=B9=8B=E5=A4=96=E6=97=B6=E4=BB=8D=E4=BB=8E?= =?UTF-8?q?=E5=90=8C=E7=9B=AE=E5=BD=95=E8=A7=A3=E6=9E=90=E7=BB=9D=E5=AF=B9?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=EF=BC=8C=E4=BF=AE=E5=A4=8D=20import=20lib=20?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E8=A7=A3=E6=9E=90=E5=8F=8A=E7=99=BD=E8=89=B2?= =?UTF-8?q?=E9=AB=98=E4=BA=AE"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 4 +-- packages/viper/src/extension.ts | 28 +++++++++++++++---- packages/viper/src/vpjEditorProvider.ts | 16 ++++++++--- .../src/analyzer/importResolver.ts | 26 +++++++++++++++-- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 03bd5e6b..7dfb1b60 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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. diff --git a/packages/viper/src/extension.ts b/packages/viper/src/extension.ts index 469eeb77..eeb7afce 100644 --- a/packages/viper/src/extension.ts +++ b/packages/viper/src/extension.ts @@ -52,28 +52,28 @@ export async function activate(context: ExtensionContext): Promise // 命令。 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 { if (!workspace.workspaceFolders || workspace.workspaceFolders.length === 0) { diff --git a/packages/viper/src/vpjEditorProvider.ts b/packages/viper/src/vpjEditorProvider.ts index 53227e82..89abe090 100644 --- a/packages/viper/src/vpjEditorProvider.ts +++ b/packages/viper/src/vpjEditorProvider.ts @@ -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) {} diff --git a/packages/vipeye-internal/src/analyzer/importResolver.ts b/packages/vipeye-internal/src/analyzer/importResolver.ts index 00244401..d801f83a 100644 --- a/packages/vipeye-internal/src/analyzer/importResolver.ts +++ b/packages/vipeye-internal/src/analyzer/importResolver.ts @@ -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.