✨ feat(vscode): 代码阅读的注释挂到它讲的代码块上成「注释」一行,重写注释改用技能一句话加结构化结果
- 普通注释不再单独成卡片:同一层挂到后面最近的块(隔空行也算),后面没有挂到前面那块,整层没代码挂到父块;只有文件开头隔着空行的注释、模块 docstring、//!、@file 单独成「文件开头的说明」 - 各语言(Python、TypeScript、Rust、Shell、Dockerfile)建好树后统一调 attachComments;块的编辑范围连着挂上的注释,差异、跳转、钉住卡片都按它 - 宿主统一给每张卡片(语言卡片、翻译插件的属性表、库规则卡片、一行译文的语句卡片)插「注释」一行,排在名称、说明后面 - TypeScript 的 JSDoc 也能当常量、变量的说明;sqlx、React 不再单独处理前面的注释 - 「重写注释」每块都有:提示只发 /ecc:writing-code-comments 路径:起-止,模型只读、按 --json-schema 交 body,没交结构化正文就失败不写;扩展换掉块前面的注释或在块上面新写一段
This commit is contained in:
1 parent
335cf9ca9f
commit
582ac4c987
49 files changed
+1730
-757
No files matched your search
@@ -5,7 +5,7 @@ import type { ImportedName } from './symbolLinks'
|
||||
import { blockAttributes } from './attributes'
|
||||
import { KIND_ICONS } from './contract'
|
||||
import { commonIndent, splitLines } from './editText'
|
||||
import { commentSpan } from './languages/shared'
|
||||
import { isCommentBlock } from './languages/shared'
|
||||
import { activeRules, claimBlock, importsIn } from './libraryRules'
|
||||
import { BlockAnchors } from './qa/anchor'
|
||||
import { importedNames } from './symbolLinks'
|
||||
@@ -38,9 +38,13 @@ interface BlockViewBase {
|
||||
* 同一个 id 只留第一个。项目入口那一块的第一个换成入口(见 entries/entryMarks.ts)。webview 按这个画图标,不自己用种类查。
|
||||
*/
|
||||
concepts: BlockConcept[]
|
||||
/** 能重写的那段注释的起止行,0 起。注释块是整块;函数、类等是它的说明。没有说明就没有,不显示重写按钮。 */
|
||||
commentStartLine?: number
|
||||
commentEndLine?: number
|
||||
/**
|
||||
* 「注释」一行(讲这块的普通注释,BlockShape.comments)排在第几行,0 起:属性表卡片是 properties 里的下标,
|
||||
* 一行译文的卡片是 rows 里的下标。没有注释就没有。
|
||||
*/
|
||||
commentRow?: number
|
||||
/** 「重写注释」写到哪(见 CommentTarget);不能写注释的块(嵌着的代码里的、语言没说行注释怎么写又还没有注释的)没有。 */
|
||||
commentTarget?: CommentTarget
|
||||
/** 库规则的卡片才有:筛选栏里这个种类的名字,卡片边框的颜色(RuleCard.badge)。图标在 concepts 里。预览同类块一样默认收起它的子块。 */
|
||||
badge?: KindBadge
|
||||
/** 库规则挂在父卡片子行上的子块才有(见 RuleSlot):展开时画在父卡片属性表第 row 行下面,不排在父卡片的子块里。 */
|
||||
@@ -55,10 +59,19 @@ export interface BlockSlot {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发给 webview 的一块(可序列化):函数、类、枚举、异常、导入、常量、变量块和库规则的卡片预览时是属性表,注释块是 Markdown 说明,其余是一行译文。
|
||||
* 一行译文下面依次是:跨几行写的调用摊开各个实参的表(TextTranslation.table),块的说明(TextBlock.doc,翻译插件给的 Markdown 原文)。
|
||||
* 「重写注释」写到哪,行号 0 起:replace 是换掉 startLine..endLine 这几行(块前面那几段普通注释;文件开头的说明卡片是它自己),
|
||||
* insert 是块还没有注释,新写一段插在 line 这一行上面(块的第一行),缩进同这一行。
|
||||
*/
|
||||
export type BlockView = BlockViewBase & ({ translation: PreviewText, table?: PreviewTable, doc?: Markdown } | { properties: PropertyRow[] })
|
||||
export type CommentTarget
|
||||
= | { kind: 'replace', startLine: number, endLine: number }
|
||||
| { kind: 'insert', line: number }
|
||||
|
||||
/**
|
||||
* 发给 webview 的一块(可序列化):函数、类、枚举、异常、导入、常量、变量块和库规则的卡片预览时是属性表,文件开头的说明是 Markdown,其余是一行译文。
|
||||
* 一行译文下面依次是:跨几行写的调用摊开各个实参的表(TextTranslation.table),块的说明(TextBlock.doc,翻译插件给的 Markdown 原文),
|
||||
* 几行属性(rows:「注释」一行)。
|
||||
*/
|
||||
export type BlockView = BlockViewBase & ({ translation: PreviewText, table?: PreviewTable, doc?: Markdown, rows?: PropertyRow[] } | { properties: PropertyRow[] })
|
||||
|
||||
interface Line { start: number, end: number }
|
||||
|
||||
@@ -211,9 +224,20 @@ function later(a: SpanEnd, b: SpanEnd): SpanEnd {
|
||||
return b.endIndex > a.endIndex ? b : a
|
||||
}
|
||||
|
||||
/** 各子孙块的头部、节点里最靠后的结尾,和 end 比取靠后的:子孙一般写在块的节点里,Dockerfile 阶段的指令写在 FROM 后面。 */
|
||||
/** 讲这块的注释(BlockShape.comments)里最靠后的结尾,和 end 比取靠后的:这一层最后一块代码后面的注释挂在它上面。 */
|
||||
function commentsEnd(block: CodeBlock, end: SpanEnd): SpanEnd {
|
||||
return (block.comments ?? []).reduce((acc, comment) => later(acc, comment.header), end)
|
||||
}
|
||||
|
||||
/** 块的完整范围从哪起:头部和讲它的注释里最靠前的那个开头(注释写在块上面)。 */
|
||||
function blockStart(block: CodeBlock): Pick<SourceSpan, 'startIndex' | 'startRow'> {
|
||||
const first = block.comments?.[0]?.header
|
||||
return first && first.startIndex < block.header.startIndex ? first : block.header
|
||||
}
|
||||
|
||||
/** 各子孙块的头部、节点、讲它的注释里最靠后的结尾,和 end 比取靠后的:子孙一般写在块的节点里,Dockerfile 阶段的指令写在 FROM 后面。 */
|
||||
function descendantsEnd(blocks: readonly CodeBlock[], end: SpanEnd): SpanEnd {
|
||||
return blocks.reduce((acc, child) => descendantsEnd(child.children, later(later(acc, child.header), nodeEnd(child.node))), end)
|
||||
return blocks.reduce((acc, child) => descendantsEnd(child.children, commentsEnd(child, later(later(acc, child.header), nodeEnd(child.node)))), end)
|
||||
}
|
||||
|
||||
/** index 之前最后一个非空白字符的结尾,不早于头部开头。 */
|
||||
@@ -224,28 +248,49 @@ function lastCharBefore(source: string, header: SourceSpan, index: number): Span
|
||||
return { endIndex: end, endRow: header.startRow + splitLines(source.slice(header.startIndex, end)).length - 1 }
|
||||
}
|
||||
|
||||
/** 一块在源码里的完整范围(whole,编辑范围),和不算讲它的注释时这条语句的范围(statement)。 */
|
||||
interface BlockSpan {
|
||||
whole: SourceSpan
|
||||
statement: SourceSpan
|
||||
}
|
||||
|
||||
/**
|
||||
* 一串兄弟块各自在源码里的完整范围:从头部开头,到头部、所在语句、各子孙块里最靠后的那个字符。
|
||||
* 一串兄弟块各自在源码里的范围:语句从头部开头,到头部、所在语句、各子孙块里最靠后的那个字符;完整范围再连同讲它的注释。
|
||||
* 语言插件把 else、catch 这类子句展平成紧跟着的兄弟块,它们的节点都落在打头那块的节点(整条 if / try)里:
|
||||
* 这条语句按各块的开头切开,每块到下一块开头之前最后一个非空白字符为止,最后一块到语句结尾(Shell 的 fi 归它)。
|
||||
* 这条语句按各块的开头切开,每块到下一块开头(连同讲它的注释)之前最后一个非空白字符为止,最后一块到语句结尾(Shell 的 fi 归它)。
|
||||
*/
|
||||
function blockSpans(list: readonly CodeBlock[], source: string): SourceSpan[] {
|
||||
function blockSpans(list: readonly CodeBlock[], source: string): BlockSpan[] {
|
||||
let statement: Node | undefined
|
||||
return list.map((block, i) => {
|
||||
if (!statement || block.node.startIndex < statement.startIndex || block.node.endIndex > statement.endIndex)
|
||||
statement = block.node
|
||||
const next = list[i + 1]
|
||||
const nextStart = next && Math.min(next.header.startIndex, next.node.startIndex)
|
||||
const nextStart = next && Math.min(blockStart(next).startIndex, next.node.startIndex)
|
||||
const until = nextStart !== undefined && nextStart < statement.endIndex ? lastCharBefore(source, block.header, nextStart) : nodeEnd(statement)
|
||||
const { endIndex, endRow } = descendantsEnd(block.children, later(block.header, until))
|
||||
return { startIndex: block.header.startIndex, endIndex, startRow: block.header.startRow, endRow }
|
||||
const end = descendantsEnd(block.children, later(block.header, until))
|
||||
const wholeEnd = commentsEnd(block, end)
|
||||
const start = blockStart(block)
|
||||
return {
|
||||
whole: { startIndex: start.startIndex, startRow: start.startRow, endIndex: wholeEnd.endIndex, endRow: wholeEnd.endRow },
|
||||
statement: { startIndex: block.header.startIndex, startRow: block.header.startRow, endIndex: end.endIndex, endRow: end.endRow },
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 「注释」一行插进属性表:排在第一行(名称这类讲它是谁的一行)后面,有说明(值是 Markdown 的一行)时排在说明后面。
|
||||
* 返回新的属性表和「注释」一行的下标。
|
||||
*/
|
||||
function withCommentRow(rows: readonly PropertyRow[], row: PropertyRow): { rows: PropertyRow[], at: number } {
|
||||
const doc = rows.findIndex(item => 'markdown' in item.value)
|
||||
const at = doc >= 0 ? doc + 1 : Math.min(1, rows.length)
|
||||
return { rows: [...rows.slice(0, at), row, ...rows.slice(at)], at }
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码块树 → 视图数据:块的完整范围整行源码去共同缩进、按语法树着色,调用翻译插件。
|
||||
* 每块先问启用的库规则,认领了就换成规则给的替身(见 RuleBlock):规则的卡片,或认成的属性表块(交给翻译插件排,同语言插件报的这种块),
|
||||
* 并进去的子块不再单独成块。
|
||||
* 并进去的子块不再单独成块。讲块的注释(BlockShape.comments)排成卡片上的「注释」一行(见 withCommentRow)。
|
||||
*/
|
||||
export function toBlockViews(
|
||||
blocks: CodeBlock[],
|
||||
@@ -281,8 +326,9 @@ export function toBlockViews(
|
||||
/**
|
||||
* 一种语言的块怎么转:用这种语言的源码、翻译插件和库规则(嵌着的代码没有库规则)。
|
||||
* 转的是一串兄弟块:各块的完整范围(编辑范围)按这一串算(见 blockSpans),id 是 prefix 接上它在这一串里的位置(从 from 数起)。
|
||||
* writable 为假时(嵌着的代码)块不能写注释:新写的行注释会写进外层语言的那几行里。
|
||||
*/
|
||||
function layer(layerBlocks: readonly CodeBlock[], layerDocument: SourceDocument, layerTranslator: Translator | undefined, layerRules: RuleRun): (list: readonly CodeBlock[], prefix: string, scope: string, from?: number) => BlockView[] {
|
||||
function layer(layerBlocks: readonly CodeBlock[], layerDocument: SourceDocument, layerTranslator: Translator | undefined, layerRules: RuleRun, writable: boolean): (list: readonly CodeBlock[], prefix: string, scope: string, from?: number) => BlockView[] {
|
||||
const imports = importsIn(layerBlocks)
|
||||
const active = activeRules(layerRules.rules, imports)
|
||||
const context: RuleContext = {
|
||||
@@ -308,7 +354,7 @@ export function toBlockViews(
|
||||
return []
|
||||
const steps = part.steps.blocks
|
||||
const [only] = steps
|
||||
return layer(steps, part.document, part.translator, NO_RULES)(steps.length === 1 && only ? only.children : steps, `${id}.`, scope, from)
|
||||
return layer(steps, part.document, part.translator, NO_RULES, false)(steps.length === 1 && only ? only.children : steps, `${id}.`, scope, from)
|
||||
}
|
||||
/**
|
||||
* 这块的概念,从左到右,同一个 id 只留第一个:语言的种类(图标、名字用内置种类和翻译插件起的种类名),
|
||||
@@ -337,9 +383,42 @@ export function toBlockViews(
|
||||
}
|
||||
return concepts
|
||||
}
|
||||
function commentLines(block: CodeBlock): { commentStartLine: number, commentEndLine: number } | undefined {
|
||||
const span = commentSpan(block)
|
||||
return span && { commentStartLine: span.startLine, commentEndLine: span.endLine }
|
||||
/** 「注释」一行:讲这块的几段注释(BlockShape.comments)各读成一段 Markdown,空行隔开;名字是翻译插件给种类 comment 起的。没有注释、没有翻译插件时没有。 */
|
||||
function commentRow(block: CodeBlock): PropertyRow | undefined {
|
||||
if (!layerTranslator || !block.comments)
|
||||
return undefined
|
||||
const parts = block.comments.flatMap((comment) => {
|
||||
const { text } = layerTranslator.translate(comment, layerDocument)
|
||||
return 'markdown' in text && text.markdown !== '' ? [text.markdown] : []
|
||||
})
|
||||
const label = layerTranslator.attributeNames?.values.kind?.comment ?? 'comment'
|
||||
return parts.length > 0 ? { label, value: { markdown: parts.join('\n\n') } } : undefined
|
||||
}
|
||||
/** 属性表卡片的属性表连同「注释」一行(见 withCommentRow)。 */
|
||||
function cardRows(block: CodeBlock, rows: PropertyRow[]): Pick<BlockView, 'commentRow'> & { properties: PropertyRow[] } {
|
||||
const row = commentRow(block)
|
||||
if (!row)
|
||||
return { properties: rows }
|
||||
const { rows: properties, at } = withCommentRow(rows, row)
|
||||
return { properties, commentRow: at }
|
||||
}
|
||||
/** 「重写注释」写到哪(见 CommentTarget):块前面的普通注释换掉,没有就新写在块上面。 */
|
||||
function commentTarget(block: CodeBlock): Pick<BlockView, 'commentTarget'> {
|
||||
if (!writable)
|
||||
return {}
|
||||
if (isCommentBlock(block))
|
||||
return { commentTarget: { kind: 'replace', startLine: block.header.startRow, endLine: block.header.endRow } }
|
||||
const leading = (block.comments ?? []).filter(comment => comment.header.startIndex < block.header.startIndex)
|
||||
const [first] = leading
|
||||
const last = leading.at(-1)
|
||||
if (first && last)
|
||||
return { commentTarget: { kind: 'replace', startLine: first.header.startRow, endLine: last.header.endRow } }
|
||||
return layerDocument.language.lineComment ? { commentTarget: { kind: 'insert', line: block.header.startRow } } : {}
|
||||
}
|
||||
/** 一行译文的卡片:译文、实参表、说明,有注释时下面再排「注释」一行。 */
|
||||
function textCard(block: TextBlock): Pick<BlockView, 'commentRow'> & ReturnType<typeof textView> & { rows?: PropertyRow[] } {
|
||||
const row = commentRow(block)
|
||||
return { ...textView(block), ...(row && { rows: [row], commentRow: 0 }) }
|
||||
}
|
||||
|
||||
/** 块照它自己的样子(语言插件报的,或库规则认成的属性表块):编辑范围是 span,头部是块自己那部分,子块照常交给各规则看。scope 见 BlockAnchors.place */
|
||||
@@ -355,19 +434,19 @@ export function toBlockViews(
|
||||
...(name && { defines: { startIndex: name.startIndex, endIndex: name.endIndex } }),
|
||||
attributes: blockAttributes(block),
|
||||
concepts: blockConcepts(block),
|
||||
...commentLines(block),
|
||||
...commentTarget(block),
|
||||
children: [
|
||||
...list(block.children, `${id}.`, inner),
|
||||
...embeddedChildren(block, id, inner, block.children.length),
|
||||
],
|
||||
...('facts' in block ? { properties: layerTranslator ? layerTranslator.describe(block, layerDocument) : [] } : textView(block)),
|
||||
...('facts' in block ? cardRows(block, layerTranslator ? layerTranslator.describe(block, layerDocument) : []) : textCard(block)),
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 块先问启用的库规则,没认领就照它自己的样子。认领了的:规则讲的是整条语句,头部就是整个范围;
|
||||
* 规则没并进来的子块(方法、别的成员)不管写在哪,都照源码顺序留作它的子块。规则略去的块(讲在了别的卡片里)不出块。
|
||||
*/
|
||||
function build(block: CodeBlock, span: SourceSpan, id: string, scope: string): BlockView | undefined {
|
||||
function build(block: CodeBlock, { whole: span, statement }: BlockSpan, id: string, scope: string): BlockView | undefined {
|
||||
const claim = active.length > 0 ? claimBlock(block, active, context, layerRules.report) : undefined
|
||||
if (!claim)
|
||||
return plain(block, span, id, scope)
|
||||
@@ -376,25 +455,25 @@ export function toBlockViews(
|
||||
return undefined
|
||||
const kept = block.children.filter(child => !replacement.absorbs.includes(child))
|
||||
if ('as' in replacement) {
|
||||
const { attributes } = block
|
||||
return plain({ ...replacement.as, node: block.node, header: span, children: kept, ...(attributes && { attributes }) }, span, id, scope)
|
||||
const { attributes, comments } = block
|
||||
return plain({ ...replacement.as, node: block.node, header: statement, children: kept, ...(attributes && { attributes }), ...(comments && { comments }) }, span, id, scope)
|
||||
}
|
||||
const { kind, badge, rows, defines, attributes } = replacement
|
||||
const slots = new Map((replacement.slots ?? []).map(({ block: child, row, item }): [CodeBlock, BlockSlot] => [child, { row, item }]))
|
||||
const { anchor, inner } = anchors.place(scope, kind, defines && source.slice(defines.startIndex, defines.endIndex), rowsText(span))
|
||||
const { anchor, inner } = anchors.place(scope, kind, defines && source.slice(defines.startIndex, defines.endIndex), rowsText(statement))
|
||||
return {
|
||||
id,
|
||||
anchor,
|
||||
kind,
|
||||
...sourceRows(span),
|
||||
headerEndLine: span.endRow,
|
||||
headerEndLine: statement.endRow,
|
||||
...(defines && { defines: { startIndex: defines.startIndex, endIndex: defines.endIndex } }),
|
||||
attributes: { ...blockAttributes(block), ...attributes, kind },
|
||||
concepts: blockConcepts(block, replacement),
|
||||
...commentLines(block),
|
||||
...commentTarget(block),
|
||||
badge,
|
||||
children: list(kept, `${id}.`, inner, 0, slots),
|
||||
properties: rows,
|
||||
...cardRows(block, rows),
|
||||
}
|
||||
}
|
||||
/** 一串兄弟块转成视图数据;略去的块不占位置,id 按出了块的往下数。slots 是父卡片挂在它属性表子行上的那几块(见 BlockSlot)。 */
|
||||
@@ -412,7 +491,7 @@ export function toBlockViews(
|
||||
return list
|
||||
}
|
||||
|
||||
return layer(blocks, document, translator, rules)(blocks, '', '')
|
||||
return layer(blocks, document, translator, rules, true)(blocks, '', '')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user