✨ feat(vscode): 新增「了解一块代码」:AI 找功能代码占一栏,对话里接着改

主栏标题栏的放大镜问要了解哪一块,占一个栏位建一栏(和收藏共用 30 个栏位,视图改名 slot1..30,按 slotKindN 分收藏 / 功能挂标题栏按钮),同时开对话页让 Claude 只读地在全部工作区文件夹里找,按 --json-schema 交回环节 → 文件 + 符号 + 一句话。
对话复用预览顶部的聊天(CodeQa.askTop:会话续接、进行中的一轮、记录存 workspaceState);结构不合格算这一轮没答成、留着能再画出来。
条目每次画、点时按眼下的代码找:有语言插件的解析成代码块按「类名.方法名」找,没有的问 VS Code 文档符号;找不到画成灰的。
This commit is contained in:
cruldra committed 2026-10-12 15:48:54 +11:00
1 parent 16d27004d8
commit 880bf7fc57
38 files changed
+3386 -378

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
import type { SlotKind } from './slots'
import { commands, window } from 'vscode'
import { freeSlot, slotContext, slotViewId } from './slots'
/**
* 收藏和功能共用的那些栏位眼下被谁占着。各自的记录(收藏的目录、找到的功能)各存各的,占哪一栏都记在这里:
* 新占时挑最小的空位(见 freeSlot),占上、放开时跟着设上下文键,那一栏和它标题栏上的按钮才显示、藏起。
*/
export class SlotPool {
private readonly kinds = new Map<number, SlotKind>()
/** 眼下没被占的里面最小的栏位;都占满了没有。 */
free(): number | undefined {
return freeSlot(this.kinds.keys())
}
/** 眼下被占着的全部栏位 */
taken(): number[] {
return [...this.kinds.keys()]
}
/** kind 占上第 slot 栏(开窗口时各自存着的,和新占的)。马上记下,同时再占的不会挑到同一栏。 */
async occupy(slot: number, kind: SlotKind): Promise<void> {
this.kinds.set(slot, kind)
await commands.executeCommand('setContext', slotContext(slot), kind)
}
/** 放开第 slot 栏:这一栏藏起来,栏位给下一个用。 */
async release(slot: number): Promise<void> {
this.kinds.delete(slot)
await commands.executeCommand('setContext', slotContext(slot), false)
}
}
/** 弹顶部的输入框问栏标题叫什么(原值整段选中);Esc 或点别处是不改。 */
export async function askSlotName(title: string, value: string): Promise<string | undefined> {
const name = await window.showInputBox({
title,
value,
prompt: '栏标题上显示的名字',
validateInput: text => text.trim() ? undefined : '名字不能为空',
})
return name?.trim()
}
/** 亮出第 slot 栏(VS Code 给每个视图自带一条 `<视图 id>.focus` 命令)。 */
export async function focusSlot(slot: number): Promise<void> {
await commands.executeCommand(`${slotViewId(slot)}.focus`)
}
+29
View File
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest'
import { freeSlot, slotContext, SLOTS, slotTitle, slotViewId } from './slots'
describe('新占栏位占哪个', () => {
it('没被占的里面最小的:中间空出来的先用', () => {
expect(freeSlot([])).toBe(1)
expect(freeSlot([1, 2])).toBe(3)
expect(freeSlot([1, 3, 4])).toBe(2)
expect(freeSlot([5])).toBe(1)
})
it(`${SLOTS.length} 个栏位都占满了:没有`, () => {
expect(SLOTS).toEqual(Array.from({ length: 30 }, (_, index) => index + 1))
expect(freeSlot(SLOTS)).toBeUndefined()
expect(freeSlot(SLOTS.slice(0, -1))).toBe(SLOTS.length)
})
})
describe('栏位对应的视图、上下文键、标题', () => {
it('第 n 个栏位有自己的视图 id 和记着被谁占着的上下文键', () => {
expect(slotViewId(3)).toBe('superpowers.codeReader.slot3')
expect(slotContext(3)).toBe('superpowers.codeReader.slotKind3')
})
it('标题前的记号分开收藏(文件夹)和功能(放大镜)', () => {
expect(slotTitle('favorite', '后端接口')).toBe('📁 后端接口')
expect(slotTitle('feature', '用户登录')).toBe('🔍 用户登录')
})
})
+35
View File
@@ -0,0 +1,35 @@
/**
* 代码阅读侧栏里收藏目录、「了解一块代码」找到的功能共用的栏位:VS Code 的视图要在 package.json 里预先声明,
* 所以声明了固定这么多个栏(superpowers.codeReader.slot1 … slot30),有收藏或功能占着的才显示。这里是栏位号 1、2、3…
*/
export const SLOTS: readonly number[] = Array.from({ length: 30 }, (_, index) => index + 1)
/** 一个栏位被谁占着:favorite 是收藏的目录,feature 是「了解一块代码」找到的一个功能。 */
export type SlotKind = 'favorite' | 'feature'
/** 第 slot 个栏的视图 id */
export function slotViewId(slot: number): string {
return `superpowers.codeReader.slot${slot}`
}
/** 上下文键:第 slot 个栏位被哪种占着(值是 SlotKind),这一栏才显示、标题栏上挂这种的按钮(package.json 的 when 按它) */
export function slotContext(slot: number): string {
return `superpowers.codeReader.slotKind${slot}`
}
/** 栏标题前的记号:收藏是文件夹,功能是放大镜。视图标题只能是字,图标字体画不进去,所以用 emoji。 */
const SLOT_MARKS: Record<SlotKind, string> = { favorite: '📁', feature: '🔍' }
/** 栏标题:记号加名字。 */
export function slotTitle(kind: SlotKind, name: string): string {
return `${SLOT_MARKS[kind]} ${name}`
}
/**
* 新占一个栏位占哪个:没被占的里面最小的,都占满了没有。taken 是收藏、功能眼下占着的全部栏位。
* 一个栏位一直被占着、删掉才空出来,用户拖动排过的位置 VS Code 按栏位记着,所以不会错乱。
*/
export function freeSlot(taken: Iterable<number>): number | undefined {
const used = new Set(taken)
return SLOTS.find(slot => !used.has(slot))
}