✨ feat(vscode): 代码阅读侧栏右键菜单加「在 Finder 中显示」,按系统换成文件资源管理器 / 打开所在的文件夹
This commit is contained in:
1 parent
430f98ade4
commit
5ce2e904f4
5 files changed
+121
-3
No files matched your search
@@ -77,6 +77,13 @@
|
||||
- 写剪贴板用 `vscode.env.clipboard.writeText`,状态栏 `已复制:<内容>` 停 3 秒,连着复制只留最新一条。
|
||||
- 命令(`copyFileName` / `copyFolderName` / `copyFileStem` / `copyRelativePath` / `copyAbsolutePath`)只挂在侧栏行上,命令面板里藏起来(`when: false`)。文件名、文件夹名复制的是同一样东西,分两个命令只为菜单上写对字,按 `viewItem =~ /^file( noted)?$/`、`/^folder( noted)?$/` 挑。
|
||||
|
||||
### 在系统文件管理器中显示
|
||||
|
||||
- 右键菜单里复制那组后面单独一组(中间有分隔线),再往后是注释:在系统的文件管理器里显示这一行。字和 VS Code 自带资源管理器一样按系统叫:macOS「在 Finder 中显示」、Windows「在文件资源管理器中显示」、Linux「打开所在的文件夹」。文件、目录都有(有没有注释、在不在「更改」组都一样,审查时也有);工作区根节点、分组行没有。只在右键菜单里,行尾没有按钮。
|
||||
- 做法:交给 VS Code 自带的命令 `revealFileInOS`,传这一行的 `Uri`(`explorer/revealCommands.ts`):文件是打开所在文件夹并选中它,目录是在上一层里选中它。
|
||||
- 命令(`revealInFinder` / `revealInFileExplorer` / `openContainingFolder`)做的事一样,分三个只为菜单上按系统写对字:`when` 分别带 `isMac` / `isWindows` / `isLinux`,每个系统只出自己那一项;命令面板里藏起来(`when: false`)。
|
||||
- 排在复制和注释中间靠组名:VS Code 按组名的字母序排右键菜单的组,组名 `fileManager` 落在 `copy` 和 `notes` 之间。
|
||||
|
||||
### 文件注释
|
||||
|
||||
读代码时给目录或文件随手记一句(原型画板「文件注释 · 悬停出按钮 / 填写 / 已保存」)。
|
||||
@@ -2118,6 +2125,7 @@ vscode/src/codeReader/
|
||||
explorer/fileTree.ts 侧栏文件树(FileTreeDecorator:给行添信息的接口;showChanges:审查工单时只摆 PR 改过的文件;nodeOf / getParent:按 Uri 找树上那一行、找上一层)
|
||||
explorer/copyText.ts 复制菜单的命令和复制出什么(纯函数)
|
||||
explorer/copyCommands.ts 复制命令:写剪贴板、状态栏提示
|
||||
explorer/revealCommands.ts 在系统文件管理器中显示:按系统叫法的三个命令都交给 VS Code 的 revealFileInOS
|
||||
review/changedFiles.ts 改动状态 → M / A / D、颜色,PR 改过的文件 → 按目录分好的树(纯函数)
|
||||
review/reviewRules.ts 能审查的列(「审查」「进行中」)、弹框列哪些工单、PR 号、能不能审查、标题栏的字(纯函数)
|
||||
review/pickList.ts 选工单弹框的各项:先摆出来、文件数读到一个换一个、还转不转圈、每项的字(纯函数)
|
||||
@@ -2224,7 +2232,7 @@ vscode/webview-ui/code-reader.html + src/codeReader/ 预览界面(React,Vit
|
||||
- 异常(`languages/exceptions.test.ts`,真实解析 + 中文直译 + 视图数据):`fixtures/appExceptions.py` 的 `AppException`、`NotFound` 逐字同「异常」的样例(列名随有没有类型、说明变),`GatewayTimeout` 的多段说明,方法是子块;`fixtures/commerceExceptions.py` 16 个类都是异常、只有说明的四行、写了 `__init__` 的构造参数;认哪些类(自带的异常类、`Warning`、不合后缀的自带类 `StopIteration` / `KeyboardInterrupt`、`pkg.errors.AppException`、同一文件里写在后面的父类一层层往下传、泛型和别的父类混写,关键字参数、`Mapping[...]`、`*bases` 不算,函数里的类也认);类属性(带不带注解、只写注解、说明取上面的注释块 / 行尾注释 / 属性 docstring,连着赋值、解构赋值、方法、讲方法的注释、最后面的注释留作子块),构造参数去掉 self、`*args` 开头的不去;`@dataclass` 的异常类照样是异常卡片;编辑范围是整个类、改卡片写回整个类,类名、属性名、参数名的定义范围和带下标的词指回源码原文;TypeScript `fixtures/exceptions.ts.txt`(`extends Error`、类字段含 static、参数属性、JSDoc、写在后面的父类、类表达式、`ns.X<T>`,父类名不合习惯的照常是类)、JavaScript 的类字段;TypeScript 和 Python 同样的异常得到一样的行和列名;筛选的种类叫「异常」、没有 badge;属性的中文名和去掉行尾记号。webview:异常块默认折叠(`viewState.test.ts`),跳到类名闪整块、属性名 / 构造参数名只闪表里那一行(`reveal.test.ts`)。
|
||||
- 筛选:块的属性(TS、TSX 有抽象、仅类型,JavaScript / JSX 没有这两个属性、属性表也没有这两行,Python 有访问级别、异步);语言插件自己报的属性叠上去、盖过推出来的,kind 不变;文件里出现过的属性按契约顺序排、语言的排后面,值按第一次出现排、带所有层的块数,名字取翻译插件的、没有的显示 id,中文直译的名字和属性表一致;下拉框只给两种以上值的属性(或一种值正被去掉的),按钮上的字;全选 / 取消全选只动这份文件里的值、别的文件的去掉状态和别的属性不动、取消全选藏掉有这个属性的块(整棵子树)、再全选全回来;同一属性内「或」、不同属性「且」、没有这个属性不受影响、藏整棵子树、「已隐藏」只数直接筛掉的;筛选记在 `workspaceState`、新实例读得回来、所有订阅的预览收到、形状不对的当没有;跳到被筛掉的块临时露出路径、目标的子块照常筛、文档刷新只留正在编辑的。
|
||||
- 文件注释:键相对工作区文件夹、`/` 分隔,根和外面的路径没有键,手写的 `./`、结尾 `/` 规范掉;保存去空白、清空即删;文件改名、目录挪位置里面各层一起走、名字只是前缀相同的不动、跨工作区文件夹先取出再放进去;删目录连带里面的;读写:文件缺失 / 解析失败 / 不是对象返回空表、跳过不合法条目、建 `.spx`、键排序、删光留 `{}`;有注释 / 没注释的行的描述、悬停提示(Markdown 转义)、contextValue,和 `package.json` 里菜单的 when 子句对得上。
|
||||
- 侧栏行上的菜单(`explorer/rowMenus.test.ts`,读 `package.json`):行尾按钮先「复制」子菜单再注释,有没有注释换「添加 / 编辑注释」,工作区根都没有;「复制」子菜单带 `$(copy)`,注释图标 `$(note)` / `$(edit)`;子菜单和右键菜单里文件四项、目录三项(有没有注释一样)、菜单上的字;复制命令都登记了、和代码里的命令表一致、命令面板里藏起来。
|
||||
- 侧栏行上的菜单(`explorer/rowMenus.test.ts`,读 `package.json`):行尾按钮先「复制」子菜单再注释,有没有注释换「添加 / 编辑注释」,工作区根都没有;「复制」子菜单带 `$(copy)`,注释图标 `$(note)` / `$(edit)`;子菜单和右键菜单里文件四项、目录三项(有没有注释一样)、菜单上的字;复制命令都登记了、和代码里的命令表一致、命令面板里藏起来;「在系统文件管理器中显示」文件和目录(含未提交、有注释)都有、工作区根和分组行没有、审查时也有,三个系统各只出自己那一项、字和 VS Code 自带的一样,自成一组排在复制和注释中间,不挂行尾按钮、命令面板里藏起来。
|
||||
- 侧栏定位:树上一行和要找的路径的关系(自己、上层目录、名字开头相同的兄弟目录不算、别的协议不算);`explorer.autoReveal` 的 `true` / `false` / `focusNoScroll` / 没读到;`autoRevealExclude` 命中路上任何一层都不跟(`explorer/treeReveal.test.ts`)。树的数据源(`explorer/fileTree.test.ts`,假的 vscode 和磁盘):多层目录 `getParent` 交回 `getChildren` 给过的同一个对象、根的上一层没有;`nodeOf` 找到文件、目录,顺着 `getParent` 走回根;工作区外、`files.exclude` 藏了的、盘上没有的找不到;只摆改动的文件时在就找到、上一层一直走到分组行,没摆的找不到,同一文件在两组里取最先摆的,分组没名字时根在最上面。
|
||||
- 审查工单:
|
||||
- 改动的文件树(`review/changedFiles.test.ts`):原型那 6 个文件加一个改名的,目录层级照留、每层只有通往改过的文件的目录,文件带改动和相对仓库根的路径,目录没有改动;排法同普通的树;文件换成同名目录两项都留着;没有文件时根下面是空的。状态 `added` → A、`deleted` → D、别的都是 M,三种颜色是 git 装饰色、删掉的提示「PR 里删掉了」。
|
||||
|
||||
@@ -189,6 +189,21 @@
|
||||
"title": "复制绝对路径",
|
||||
"category": "代码阅读"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.revealInFinder",
|
||||
"title": "在 Finder 中显示",
|
||||
"category": "代码阅读"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.revealInFileExplorer",
|
||||
"title": "在文件资源管理器中显示",
|
||||
"category": "代码阅读"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.openContainingFolder",
|
||||
"title": "打开所在的文件夹",
|
||||
"category": "代码阅读"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.addNote",
|
||||
"title": "添加注释",
|
||||
@@ -365,6 +380,21 @@
|
||||
"when": "view == superpowers.codeReader.files && viewItem =~ /^(file( unstaged)?|folder)( noted)?$/",
|
||||
"group": "copy@4"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.revealInFinder",
|
||||
"when": "view == superpowers.codeReader.files && isMac && viewItem =~ /^(file( unstaged)?|folder)( noted)?$/",
|
||||
"group": "fileManager"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.revealInFileExplorer",
|
||||
"when": "view == superpowers.codeReader.files && isWindows && viewItem =~ /^(file( unstaged)?|folder)( noted)?$/",
|
||||
"group": "fileManager"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.openContainingFolder",
|
||||
"when": "view == superpowers.codeReader.files && isLinux && viewItem =~ /^(file( unstaged)?|folder)( noted)?$/",
|
||||
"group": "fileManager"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.addNote",
|
||||
"when": "view == superpowers.codeReader.files && superpowers.codeReader.filter != review && viewItem =~ /^(file( unstaged)?|folder)$/",
|
||||
@@ -445,6 +475,18 @@
|
||||
"command": "superpowers.codeReader.copyAbsolutePath",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.revealInFinder",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.revealInFileExplorer",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.openContainingFolder",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "superpowers.codeReader.addNote",
|
||||
"when": "false"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { FileNode } from './fileTree'
|
||||
import { commands, Disposable } from 'vscode'
|
||||
|
||||
/**
|
||||
* 在系统的文件管理器里显示这一行,菜单上的字跟 VS Code 自带资源管理器一样按系统叫:
|
||||
* macOS「在 Finder 中显示」、Windows「在文件资源管理器中显示」、Linux「打开所在的文件夹」。
|
||||
* 做的事一样,分成三个命令只为菜单上写对字(package.json 按 `isMac` / `isWindows` / `isLinux` 挑)。
|
||||
*/
|
||||
const REVEAL_COMMANDS = [
|
||||
'superpowers.codeReader.revealInFinder',
|
||||
'superpowers.codeReader.revealInFileExplorer',
|
||||
'superpowers.codeReader.openContainingFolder',
|
||||
]
|
||||
|
||||
/** 交给 VS Code 自带的 `revealFileInOS`:文件是打开所在文件夹并选中它,文件夹是在上一层里选中它。 */
|
||||
async function revealInOS(node: FileNode | undefined): Promise<void> {
|
||||
if (!node)
|
||||
return // 只挂在侧栏的行上,别处调用没有目标
|
||||
await commands.executeCommand('revealFileInOS', node.uri)
|
||||
}
|
||||
|
||||
export function registerRevealCommands(): Disposable {
|
||||
return Disposable.from(...REVEAL_COMMANDS.map(id => commands.registerCommand(id, revealInOS)))
|
||||
}
|
||||
@@ -73,7 +73,7 @@ describe('侧栏行尾按钮', () => {
|
||||
})
|
||||
|
||||
it('分组行(「暂存的更改」「更改」)什么按钮、菜单都没有', () => {
|
||||
for (const group of ['inline', 'copy', 'notes'])
|
||||
for (const group of ['inline', 'copy', 'fileManager', 'notes'])
|
||||
expect(itemsFor('view/item/context', group, GROUP_CONTEXT)).toEqual([])
|
||||
expect(itemsFor(COPY_MENU, 'copy', GROUP_CONTEXT)).toEqual([])
|
||||
})
|
||||
@@ -136,6 +136,48 @@ describe('右键菜单的注释', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('右键菜单的「在系统文件管理器中显示」', () => {
|
||||
/** 菜单上的字跟着系统走,和 VS Code 自带资源管理器一样 */
|
||||
const REVEALS = [
|
||||
{ platform: 'isMac', command: 'superpowers.codeReader.revealInFinder', title: '在 Finder 中显示' },
|
||||
{ platform: 'isWindows', command: 'superpowers.codeReader.revealInFileExplorer', title: '在文件资源管理器中显示' },
|
||||
{ platform: 'isLinux', command: 'superpowers.codeReader.openContainingFolder', title: '打开所在的文件夹' },
|
||||
]
|
||||
const REVEAL_IDS = REVEALS.map(item => item.command)
|
||||
const whenOf = (command: string): string | undefined => menus['view/item/context'].find(item => item.command === command)?.when
|
||||
|
||||
it('文件、文件夹(含未提交、有注释)都有,工作区根没有;审查时也不藏', () => {
|
||||
for (const context of [file, folder, unstaged, noted(file), noted(folder), noted(unstaged)])
|
||||
expect(itemsFor('view/item/context', 'fileManager', context)).toEqual(REVEAL_IDS)
|
||||
expect(itemsFor('view/item/context', 'fileManager', root)).toEqual([])
|
||||
expect(REVEAL_IDS.some(id => whenOf(id)?.includes('filter'))).toBe(false)
|
||||
})
|
||||
|
||||
it('每个系统只出自己那一项,字和 VS Code 自带的一样', () => {
|
||||
for (const { platform, command, title } of REVEALS) {
|
||||
const when = whenOf(command)
|
||||
expect(when).toContain(`&& ${platform} &&`)
|
||||
for (const other of REVEALS.filter(item => item.platform !== platform))
|
||||
expect(when).not.toContain(other.platform)
|
||||
expect(commands.find(item => item.command === command)?.title).toBe(title)
|
||||
}
|
||||
})
|
||||
|
||||
it('自成一组,排在复制后面、注释前面(VS Code 按组名的字母序排组,组和组之间画分隔线)', () => {
|
||||
const groups = menus['view/item/context']
|
||||
.filter(item => item.when.includes('view == superpowers.codeReader.files') && item.group !== undefined && !item.group.startsWith('inline'))
|
||||
.map(item => item.group!.split('@')[0])
|
||||
expect([...new Set(groups)].sort((a, b) => a.localeCompare(b))).toEqual(['copy', 'fileManager', 'notes'])
|
||||
})
|
||||
|
||||
it('只在右键菜单里,不挂行尾按钮,命令面板里藏起来', () => {
|
||||
for (const id of REVEAL_IDS) {
|
||||
expect(menus['view/item/context'].filter(item => item.command === id)).toHaveLength(1)
|
||||
expect(menus.commandPalette.find(item => item.command === id)?.when).toBe('false')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('筛选(审查工单、未提交的更改)', () => {
|
||||
const FILTER = 'superpowers.codeReader.filter'
|
||||
const PICK = 'superpowers.codeReader.pickReviewIssue'
|
||||
|
||||
@@ -17,6 +17,7 @@ import { WorkspaceEntries } from './entries/workspaceEntries'
|
||||
import { registerCopyCommands } from './explorer/copyCommands'
|
||||
import { FileTreeProvider } from './explorer/fileTree'
|
||||
import { registerPathAsk } from './explorer/pathAskPanel'
|
||||
import { registerRevealCommands } from './explorer/revealCommands'
|
||||
import { RowDecorator } from './explorer/rowDecorator'
|
||||
import { TreeFilters } from './explorer/treeFilters'
|
||||
import { TreeLocator } from './explorer/treeLocator'
|
||||
@@ -71,7 +72,7 @@ async function openFile(uri: Uri, registry: CodeReaderRegistry): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 接入代码阅读:注册表、侧栏文件树(问路径、复制、文件注释、筛选:按审查工单只看 PR 改过的文件、只看未提交的更改)、预览编辑器(含问 AI)、编辑器右上角的「预览 / 编辑」切换。
|
||||
* 接入代码阅读:注册表、侧栏文件树(问路径、复制、在系统文件管理器里显示、文件注释、筛选:按审查工单只看 PR 改过的文件、只看未提交的更改)、预览编辑器(含问 AI)、编辑器右上角的「预览 / 编辑」切换。
|
||||
* 内置的语言插件、翻译插件、库规则和命令规则与其他扩展一样通过返回的 API 注册。
|
||||
*/
|
||||
export function registerCodeReader(context: ExtensionContext): CodeReaderApi {
|
||||
@@ -108,6 +109,7 @@ export function registerCodeReader(context: ExtensionContext): CodeReaderApi {
|
||||
indexes,
|
||||
registerNoteCommands(notes),
|
||||
registerCopyCommands(),
|
||||
registerRevealCommands(),
|
||||
registerPathAsk(
|
||||
async (uri) => {
|
||||
const languageId = await languageOf(uri)
|
||||
|
||||
Reference in new issue
Block a user