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.