✨ feat(vscode): 新增「了解一块代码」:AI 找功能代码占一栏,对话里接着改
主栏标题栏的放大镜问要了解哪一块,占一个栏位建一栏(和收藏共用 30 个栏位,视图改名 slot1..30,按 slotKindN 分收藏 / 功能挂标题栏按钮),同时开对话页让 Claude 只读地在全部工作区文件夹里找,按 --json-schema 交回环节 → 文件 + 符号 + 一句话。 对话复用预览顶部的聊天(CodeQa.askTop:会话续接、进行中的一轮、记录存 workspaceState);结构不合格算这一轮没答成、留着能再画出来。 条目每次画、点时按眼下的代码找:有语言插件的解析成代码块按「类名.方法名」找,没有的问 VS Code 文档符号;找不到画成灰的。
This commit is contained in:
1 parent
16d27004d8
commit
880bf7fc57
38 files changed
+3386
-378
No files matched your search
@@ -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`)
|
||||
}
|
||||
@@ -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('🔍 用户登录')
|
||||
})
|
||||
})
|
||||
@@ -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))
|
||||
}
|
||||
Reference in new issue
Block a user