-
Notifications
You must be signed in to change notification settings - Fork 13
Fix#28 #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Grenz1inie
wants to merge
4
commits into
slightc:main
Choose a base branch
from
Grenz1inie:fix#28
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Fix#28 #31
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| [github-shield]: https://img.shields.io/github/stars/slightc/pip-manager?style=social | ||
| [github-url]: https://github.com/slightc/pip-manager | ||
| [vscode-shield]: https://img.shields.io/visual-studio-marketplace/i/slightc.pip-manager?logo=visual-studio-code&style=social | ||
| [vscode-url]: https://marketplace.visualstudio.com/items?itemName=slightc.pip-manager | ||
|
|
||
| [![VSCode Plugin][vscode-shield]][vscode-url] | ||
| [![Github Repo][github-shield]][github-url] | ||
|
|
||
| # Pip Manager README | ||
|
|
||
| 在 VS Code 中管理 Python 包 | ||
|
|
||
| 如果你喜欢这个扩展,请在 GitHub 上给它加星(star):[在 GitHub 上查看](https://github.com/slightc/pip-manager),谢谢! | ||
|
|
||
| ### 入口 | ||
| * [在 VS Code 市场](https://marketplace.visualstudio.com/items?itemName=slightc.pip-manager) | ||
| * [在 GitHub](https://github.com/slightc/pip-manager) | ||
|
|
||
| ## 功能 | ||
|
|
||
| ### 列出所有 Python 包 | ||
|  | ||
|
|
||
| ### 安装 Python 包 | ||
|  | ||
|
|
||
| ### 卸载/移除 Python 包 | ||
|  | ||
|
|
||
| ### 搜索 Python 包 | ||
|  | ||
|
|
||
| ### 包更新检查 | ||
|  | ||
|
|
||
| ### 选择包版本 | ||
|  | ||
|
|
||
| ### 其他功能 | ||
| * 在 PyPI 打开包的描述页面 | ||
| * 复制包名 | ||
| * 从 requirements.txt 安装包 | ||
| * 可选择要使用的 Python 解释器/来源 | ||
|
|
||
|
|
||
| ## 要求 | ||
|
|
||
| 必须安装 Python 扩展(ms-python.python) | ||
|
|
||
| ## 已知问题 | ||
|
|
||
| (暂无) | ||
|
|
||
| ## 发行说明 | ||
|
|
||
| ### 1.1.0 | ||
|
|
||
| 添加:选择包版本功能 | ||
|
|
||
| 添加:安装来源配置 | ||
|
|
||
| ### 1.0.15 | ||
|
|
||
| 添加:包更新检查 | ||
|
|
||
| 修复:Windows 上 installRequirements 错误 | ||
|
|
||
| ### 1.0.13 | ||
|
|
||
| 修复:本地化(localize) 错误 | ||
|
|
||
| ### 1.0.12 | ||
|
|
||
| 添加:从 PyPI 搜索包 | ||
|
|
||
| ### 1.0.5 | ||
|
|
||
| 添加:在 PyPI 打开包描述页面 | ||
| ### 1.0.0 | ||
|
|
||
| Pip Manager 初始发布 | ||
|
|
||
|
|
||
| 祝使用愉快! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,6 @@ import * as path from 'path'; | |
| import * as os from 'os'; | ||
| import * as vscode from 'vscode'; | ||
| import axios from 'axios'; | ||
| import * as xml2js from 'xml2js'; | ||
| import * as utils from '@/utils'; | ||
| import { createDecorator } from '@/common/ioc/common/instantiation'; | ||
| import { IExtensionContext, IOutputChannel } from '@/interface/common'; | ||
|
|
@@ -150,7 +149,7 @@ export class PackageManager implements IPackageManager { | |
| } | ||
| }); | ||
|
|
||
| p.on('close', (code) => { | ||
| p.on('close', (code: number) => { | ||
| this.output.appendLine(''); | ||
| if (!code) { | ||
| resolve(out); | ||
|
|
@@ -305,55 +304,104 @@ export class PackageManager implements IPackageManager { | |
| } | ||
|
|
||
| public async searchFromPyPi(keyword: string, page = 1, cancelToken?: vscode.CancellationToken) { | ||
| // 新实现:使用 PyPI Simple v1 JSON 索引获取项目列表, // New implementation: use the PyPI Simple v1 JSON index to get the project list | ||
| // 然后对当前页面的包逐个请求 /pypi/<name>/json 获取元数据 // Then fetch metadata for each package on the current page via /pypi/<name>/json | ||
| // 这样比解析 HTML 更稳定,也更符合 PyPI 的使用政策。 // This is more stable than parsing HTML and complies better with PyPI policies. | ||
| // 同时保留 cancelToken 支持,能取消正在进行的 axios 请求。 // Keeps cancelToken support so in-flight axios requests can be cancelled | ||
| const axiosCancelToken = utils.createAxiosCancelToken(cancelToken); | ||
| const resp = await axios({ | ||
| method: 'GET', | ||
|
|
||
| // 获取 Simple 索引(请求头指定 vnd.pypi.simple.v1+json) // Fetch the Simple index (request header specifies vnd.pypi.simple.v1+json) | ||
| // 注意:此请求可能返回多种 JSON 结构,下面做了兼容处理 // Note: the response may have different JSON shapes; handle variants below | ||
| const indexResp = await axios.get('https://pypi.org/simple/', { | ||
| headers: { Accept: 'application/vnd.pypi.simple.v1+json' }, | ||
| cancelToken: axiosCancelToken.token, | ||
| url: `https://pypi.org/search/?q=${keyword}&page=${page}${keyword ? '' : `&c=${defaultCategory}` | ||
| }`, | ||
| }); | ||
| const [resultXml] = | ||
| RegExp( | ||
| '<ul class="unstyled" aria-label="Search results">[\\s\\S]*?</ul>' | ||
| ).exec(resp.data) || []; | ||
| if (!resultXml) {return Promise.reject({ type: 'no result' });} | ||
| const [paginationXml] = | ||
| RegExp( | ||
| '<div class="button-group button-group--pagination">[\\s\\S]*?</div>' | ||
| ).exec(resp.data) || []; | ||
| const result = await xml2js.parseStringPromise(resultXml, { | ||
| explicitArray: false, | ||
| }); | ||
|
|
||
| const list: PackagePickItem[] = []; | ||
| result.ul.li.forEach((item: any) => { | ||
| const data = { | ||
| name: item.a.h3.span[0]._, | ||
| version: item.a.h3.span[1]._, | ||
| updateTime: item.a.h3.span[2].time.$.datetime, | ||
| describe: item.a.p._, | ||
| }; | ||
| list.push({ | ||
| name: data.name, | ||
| version: data.version, | ||
| alwaysShow: true, | ||
| label: data.name, | ||
| description: `${data.version}`, | ||
| detail: data.describe | ||
| // indexResp.data 可能是 { projects: [...] } 或数组 - 两种格式都做兼容处理 // indexResp.data may be { projects: [...] } or an array - handle both formats | ||
| let projects: string[] = []; | ||
| try { | ||
| if (Array.isArray(indexResp.data)) { | ||
| // 有些服务器可能直接返回包名字符串数组或对象数组 // Some servers may return an array of package name strings or objects | ||
| projects = indexResp.data.map((p: any) => (typeof p === 'string' ? p : p.name)).filter(Boolean); | ||
| } else if (Array.isArray(indexResp.data.projects)) { | ||
| projects = indexResp.data.projects.map((p: any) => (typeof p === 'string' ? p : p.name)).filter(Boolean); | ||
| } else if (indexResp.data && typeof indexResp.data === 'object' && Array.isArray(indexResp.data.entries)) { | ||
| // 回退处理:尝试从 entries 字段中提取 name // Fallback: try to extract 'name' from the entries field | ||
| projects = indexResp.data.entries.map((p: any) => (p && p.name) || '').filter(Boolean); | ||
| } | ||
| } catch (err) { | ||
| // 解析失败则视为无搜索结果 // If parsing fails, treat as no search result | ||
| return Promise.reject({ type: 'no result' }); | ||
| } | ||
|
|
||
| // 不区分大小写的过滤(在索引中查找包含关键字的包名) // Case-insensitive filtering (find package names that include the keyword) | ||
| const needle = (keyword || '').trim(); | ||
| const lneedle = needle.toLowerCase(); | ||
| let matched = needle ? projects.filter((n) => n.toLowerCase().includes(lneedle)) : projects.slice(); | ||
|
|
||
| // 优先展示完全匹配的结果,其次是以关键字开头的包,最后才是包含关键字的包 // Prioritize exact matches, then names starting with the keyword, then names that contain it | ||
| if (needle) { | ||
| const exact: string[] = []; | ||
| const starts: string[] = []; | ||
| const others: string[] = []; | ||
| matched.forEach((n) => { | ||
| const ln = n.toLowerCase(); | ||
| if (ln === lneedle) { | ||
| exact.push(n); | ||
| } else if (ln.startsWith(lneedle)) { | ||
| starts.push(n); | ||
| } else { | ||
| others.push(n); | ||
| } | ||
| }); | ||
| }); | ||
| matched = [...exact, ...starts, ...others]; | ||
| } | ||
|
|
||
| let totalPages = 1; | ||
| if (!matched || matched.length === 0) { | ||
| return Promise.reject({ type: 'no result' }); | ||
| } | ||
|
|
||
| if (paginationXml) { | ||
| const pagination = await xml2js.parseStringPromise(paginationXml, { | ||
| explicitArray: false, | ||
| }); | ||
| totalPages = Number(pagination.div.a[pagination.div.a.length - 2]._) || 1; | ||
| if (totalPages < page) { | ||
| totalPages = page; | ||
| // 分页设置:每页请求一定数量的包元数据,过大可能导致网络/速率问题 // Pagination: request a fixed number of package metadata per page to avoid network/rate issues | ||
| const pageSize = 20; // 每页大小,保持合理以防并发请求过多 | ||
| const totalPages = Math.max(1, Math.ceil(matched.length / pageSize)); | ||
| const currentPage = Math.max(1, Math.min(page, totalPages)); | ||
| const start = (currentPage - 1) * pageSize; | ||
| const pageItems = matched.slice(start, start + pageSize); | ||
|
|
||
| // 为当前页的包并行获取元数据(支持取消) // Fetch metadata for current-page packages in parallel (supports cancellation) | ||
| // 说明:并行请求提升速度,但会同时产生多个网络请求, // Note: parallel requests improve speed but create multiple network calls | ||
| // 可根据需要调整 pageSize 或实现请求节流/缓存。 // Consider adjusting pageSize or implementing throttling/caching if needed | ||
| const list: PackagePickItem[] = []; | ||
| await Promise.all(pageItems.map(async (name) => { | ||
| // 如果外部触发了取消(CancellationToken),axiosCancelToken.token.reason 会被设置 // If external cancellation is triggered (CancellationToken), axiosCancelToken.token.reason will be set | ||
| if (axiosCancelToken.token.reason) { return; } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Checking axiosCancelToken.token.reason for cancellation may not be robust across axios versions. This approach may break in future axios releases. Use axios's documented cancellation methods, such as catching axios.Cancel, for consistent behavior. |
||
| try { | ||
| const metaResp = await axios.get(`https://pypi.org/pypi/${encodeURIComponent(name)}/json`, { | ||
| cancelToken: axiosCancelToken.token, | ||
| }); | ||
| const info = metaResp.data && metaResp.data.info ? metaResp.data.info : undefined; | ||
| const version = info?.version || ''; | ||
| const summary = info?.summary || ''; | ||
| list.push({ | ||
| name, | ||
| version, | ||
| alwaysShow: true, | ||
| label: name, | ||
| description: `${version}`, | ||
| detail: summary, | ||
| }); | ||
| } catch (err) { | ||
| // 如果单个包的元数据请求出错,则退回到最小信息,避免整个搜索失败 // If a single package metadata fetch fails, fall back to minimal info to avoid failing the whole search | ||
| list.push({ | ||
| name, | ||
| version: '', | ||
| alwaysShow: true, | ||
| label: name, | ||
| description: '', | ||
| detail: '', | ||
| }); | ||
| } | ||
| } | ||
| })); | ||
|
|
||
| return { | ||
| list, | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # 欢迎使用你的 VS Code 扩展 | ||
|
|
||
| ## 文件夹里有什么 | ||
|
|
||
| * 此文件夹包含扩展所需的所有文件。 | ||
| * `package.json` - 插件的清单文件,在这里声明你的扩展和命令。 | ||
| * 示例插件注册了一个命令并定义了它的标题和命令名。借助这些信息,VS Code 可以在命令面板中显示该命令。此时并不需要立即加载插件。 | ||
| * `src/extension.ts` - 这是实现命令的主文件。 | ||
| * 该文件导出一个函数 `activate`,当扩展第一次被激活(在本例中是执行命令时)会调用它。在 `activate` 函数中我们调用 `registerCommand`。 | ||
| * 我们把包含命令实现的函数作为第二个参数传给 `registerCommand`。 | ||
|
|
||
| ## 立即开始运行 | ||
|
|
||
| * 按 `F5` 会打开一个包含已加载扩展的新窗口。 | ||
| * 按(在 Windows/Linux 上为 `Ctrl+Shift+P`,在 macOS 上为 `Cmd+Shift+P`)打开命令面板,输入 `Hello World` 来运行你的命令。 | ||
| * 在 `src/extension.ts` 中设置断点以调试你的扩展。 | ||
| * 在调试控制台可以看到来自扩展的输出。 | ||
|
|
||
| ## 进行修改 | ||
|
|
||
| * 修改 `src/extension.ts` 里的代码后,可以从调试工具栏重新启动扩展。 | ||
| * 也可以通过重新加载 VS Code 窗口(在 Windows/Linux 上为 `Ctrl+R`,在 macOS 上为 `Cmd+R`)来加载更改。 | ||
|
|
||
|
|
||
| ## 探索 API | ||
|
|
||
| * 打开 `node_modules/@types/vscode/index.d.ts` 文件可以查看我们完整的 API。 | ||
|
|
||
| ## 运行测试 | ||
|
|
||
| * 打开调试视图(在 Windows/Linux 上为 `Ctrl+Shift+D`,在 macOS 上为 `Cmd+Shift+D`),并在启动配置下拉框中选择 `Extension Tests`。 | ||
| * 按 `F5` 在带有已加载扩展的新窗口中运行测试。 | ||
| * 在调试控制台查看测试结果输出。 | ||
| * 修改 `src/test/suite/extension.test.ts` 或在 `test/suite` 文件夹中创建新的测试文件。 | ||
| * 提供的测试运行器只会考虑名称模式匹配 `**.test.ts` 的文件。 | ||
| * 你可以在 `test` 文件夹中创建子文件夹来按需组织测试。 | ||
|
|
||
| ## 更进一步 | ||
|
|
||
| * 通过[打包你的扩展](https://code.visualstudio.com/api/working-with-extensions/bundling-extension)来减小扩展体积并改善启动时间。 | ||
| * 在 VS Code 扩展市场上[发布你的扩展](https://code.visualstudio.com/api/working-with-extensions/publishing-extension)。 | ||
| * 通过设置[持续集成](https://code.visualstudio.com/api/working-with-extensions/continuous-integration)来自动化构建流程。 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Fallback to minimal info on metadata fetch failure is good, but error handling could be more granular.
Please handle cancellation errors separately from other errors to avoid adding incomplete items to the list when a request is cancelled.