🐛 fix(vscode): 代码阅读编辑父卡片时显示整块代码(含子块)
- 块的编辑范围改成它在源码里的完整范围:头部连同各子孙块、收尾括号,保存时按整段写回;子卡片只有它自己连同子孙 - 范围由宿主统一算(blockSpans),各语言插件同一条路:else / elif / catch 这类展平的兄弟块按各块开头切开整条语句,最后一块到语句结尾(Shell 的 fi 归它) - 库规则卡片的编辑范围并进同一套算法,去掉 claimedShape - 块编辑时它的子孙都不画(代码都在编辑框里),点「编辑」时重叠的祖先、子孙、共用一行的兄弟块退出编辑 - 问 AI 的外层块开头改用新加的 headerEndLine,位置记号照旧按头部算,已有问答不受影响 - 着色改成整份源码算一次、各块按行取,编辑范围变大后排视图数据不变慢
This commit is contained in:
1 parent
af63d2bc5c
commit
70ea22543f
13 files changed
+251
-143
No files matched your search
@@ -22,7 +22,7 @@ export interface BlockTreeState {
|
||||
hidden: ReadonlySet<string>
|
||||
/** 本来被筛掉、跳到定义时临时显示的块 */
|
||||
temporary: ReadonlySet<string>
|
||||
/** 被正在编辑的祖先盖住、不画的块(它们的行在祖先的编辑范围里),见 coveredByEditing */
|
||||
/** 被正在编辑的祖先盖住、不画的块(它们的代码在祖先的编辑框里),见 coveredByEditing */
|
||||
covered: ReadonlyMap<string, string>
|
||||
/** 种类叫什么(种类图标的悬停提示和 aria-label):翻译插件给种类起的名字(库规则的种类是规则给的 badge.label),没给就是 id */
|
||||
kindName: (kind: string) => string
|
||||
|
||||
@@ -8,43 +8,46 @@ function block(id: string, kind: string, [startLine, endLine]: [number, number],
|
||||
}
|
||||
|
||||
/**
|
||||
* 库规则的卡片(1)编辑范围是整个类(2–12 行),两个字段中间写着的方法(1.0)和它的主体(1.0.0)、类末尾的方法(1.1)都在这段里;
|
||||
* 普通的类(2)只编辑头部,方法(2.0)不在头部里;一行写完的 if(3)和它的返回(3.0)在同一行。
|
||||
* 编辑范围是块的完整源码:库规则的卡片(1)是整个类(2–12 行),两个字段中间写着的方法(1.0)和它的主体(1.0.0)、
|
||||
* 类末尾的方法(1.1)都在这段里;普通的类(2)同样盖住它的方法(2.0);`} else {` 写在一行时 if(3)和 else(4)共用第 20 行。
|
||||
*/
|
||||
const BLOCKS = [
|
||||
block('0', 'import', [0, 0]),
|
||||
block('1', 'sqlalchemy.table', [2, 12], [
|
||||
block('1.0', 'function', [5, 5], [block('1.0.0', 'return', [6, 6])]),
|
||||
block('1.0', 'function', [5, 6], [block('1.0.0', 'return', [6, 6])]),
|
||||
block('1.1', 'function', [11, 12]),
|
||||
]),
|
||||
block('2', 'class', [14, 14], [block('2.0', 'function', [15, 16])]),
|
||||
block('3', 'if', [18, 18], [block('3.0', 'return', [18, 18])]),
|
||||
block('2', 'class', [14, 16], [block('2.0', 'function', [15, 16])]),
|
||||
block('3', 'if', [18, 20], [block('3.0', 'return', [19, 19])]),
|
||||
block('4', 'else', [20, 22], [block('4.0', 'return', [21, 21])]),
|
||||
]
|
||||
|
||||
describe('同一行不会同时在两处能改', () => {
|
||||
it('卡片编辑时,它留下的子块(字段中间的方法连同主体)不画,跳到它们时改闪卡片', () => {
|
||||
it('块编辑时它的子孙都不画(代码都在它的编辑框里),跳到它们时改闪它', () => {
|
||||
expect(coveredByEditing(BLOCKS, new Set(['1']))).toEqual(new Map([['1.0', '1'], ['1.0.0', '1'], ['1.1', '1']]))
|
||||
expect(coveredByEditing(BLOCKS, new Set(['2']))).toEqual(new Map([['2.0', '2']]))
|
||||
})
|
||||
|
||||
it('只编辑头部的块不盖住子块;一行写完的块编辑时盖住同一行的子块', () => {
|
||||
expect(coveredByEditing(BLOCKS, new Set(['2', '2.0']))).toEqual(new Map())
|
||||
expect(coveredByEditing(BLOCKS, new Set(['3']))).toEqual(new Map([['3.0', '3']]))
|
||||
it('子块编辑时只盖住它自己的子孙,父块、兄弟块照常画', () => {
|
||||
expect(coveredByEditing(BLOCKS, new Set(['1.0']))).toEqual(new Map([['1.0.0', '1.0']]))
|
||||
expect(coveredByEditing(BLOCKS, new Set(['1.1', '3']))).toEqual(new Map([['3.0', '3']]))
|
||||
})
|
||||
|
||||
it('没在编辑的卡片不盖住子块:子块照常画、照常能单独编辑', () => {
|
||||
expect(coveredByEditing(BLOCKS, new Set(['1.0']))).toEqual(new Map())
|
||||
it('祖先和子孙都记着在编辑时(文档刷新前的状态),算页面上画着的最外层那块', () => {
|
||||
expect(coveredByEditing(BLOCKS, new Set(['1', '1.0']))).toEqual(new Map([['1.0', '1'], ['1.0.0', '1'], ['1.1', '1']]))
|
||||
})
|
||||
|
||||
it('点「编辑」:编辑范围和它重叠的别的块退出编辑,不重叠的留着', () => {
|
||||
it('点「编辑」:编辑范围和它重叠的别的块(祖先、子孙、共用一行的兄弟块)退出编辑,不重叠的留着', () => {
|
||||
expect(startEditing(BLOCKS, ['1.0', '1.1', '2'], '1')).toEqual(['2', '1'])
|
||||
expect(startEditing(BLOCKS, ['1', '2'], '1.0')).toEqual(['2', '1.0'])
|
||||
expect(startEditing(BLOCKS, ['2'], '2.0')).toEqual(['2', '2.0'])
|
||||
expect(startEditing(BLOCKS, ['2'], '2.0')).toEqual(['2.0'])
|
||||
expect(startEditing(BLOCKS, ['0', '3'], '4')).toEqual(['0', '4'])
|
||||
})
|
||||
|
||||
it('任何时候正在编辑、画出来的几块,编辑范围两两不重叠', () => {
|
||||
const all = (list: BlockView[]): BlockView[] => list.flatMap(item => [item, ...all(item.children)])
|
||||
let editing: string[] = []
|
||||
for (const id of ['1.0', '1.0.0', '2', '2.0', '1', '3.0', '3', '1.1'])
|
||||
for (const id of ['1.0', '1.0.0', '2', '2.0', '1', '3.0', '3', '4', '1.1'])
|
||||
editing = startEditing(BLOCKS, editing, id)
|
||||
const covered = coveredByEditing(BLOCKS, new Set(editing))
|
||||
const shown = all(BLOCKS).filter(item => editing.includes(item.id) && !covered.has(item.id))
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { BlockView } from './types'
|
||||
|
||||
/**
|
||||
* 同一时刻能编辑的几段源码不重叠,同一行不会同时在两处能改:
|
||||
* ① 块编辑时,编辑范围和它重叠的子孙块不画(库规则的卡片编辑整个类,留下的方法也在这段里;
|
||||
* 一行写完的 `if x: return 1`,子块和头部在同一行),它们下面的子孙也跟着不画;
|
||||
* ② 点「编辑」时,编辑范围和这块重叠的别的块退出编辑。
|
||||
* ① 块的编辑范围是它的完整源码,子孙都在它的编辑框里:块编辑时它的子孙都不画;
|
||||
* ② 点「编辑」时,编辑范围和这块重叠的别的块(祖先、子孙,`} else {` 这样共用一行的兄弟块)退出编辑。
|
||||
*/
|
||||
|
||||
type Lines = Pick<BlockView, 'startLine' | 'endLine'>
|
||||
@@ -13,22 +12,24 @@ function overlaps(a: Lines, b: Lines): boolean {
|
||||
return a.startLine <= b.endLine && b.startLine <= a.endLine
|
||||
}
|
||||
|
||||
/** 正在编辑的块盖住、不画的块 → 盖住它的那块(页面上画着的、正在编辑的祖先):跳到定义落在被盖住的块上时改闪它。 */
|
||||
/** 正在编辑的块的子孙不画 → 盖住它的那块(页面上画着的、正在编辑的祖先):跳到定义落在被盖住的块上时改闪它。 */
|
||||
export function coveredByEditing(blocks: readonly BlockView[], editing: ReadonlySet<string>): Map<string, string> {
|
||||
const covered = new Map<string, string>()
|
||||
const visit = (list: readonly BlockView[], editors: readonly BlockView[], owner: string | undefined): void => {
|
||||
const cover = (list: readonly BlockView[], by: string): void => {
|
||||
for (const block of list) {
|
||||
const by = owner ?? editors.find(editor => overlaps(editor, block))?.id
|
||||
if (by !== undefined) {
|
||||
covered.set(block.id, by)
|
||||
visit(block.children, editors, by)
|
||||
}
|
||||
else {
|
||||
visit(block.children, editing.has(block.id) ? [...editors, block] : editors, undefined)
|
||||
}
|
||||
covered.set(block.id, by)
|
||||
cover(block.children, by)
|
||||
}
|
||||
}
|
||||
visit(blocks, [], undefined)
|
||||
const visit = (list: readonly BlockView[]): void => {
|
||||
for (const block of list) {
|
||||
if (editing.has(block.id))
|
||||
cover(block.children, block.id)
|
||||
else
|
||||
visit(block.children)
|
||||
}
|
||||
}
|
||||
visit(blocks)
|
||||
return covered
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ interface BlockViewBase {
|
||||
anchor: string
|
||||
/** BlockKind,或库规则认领的块的种类 id(带点,例如 sqlalchemy.table);webview 不认识后者,只按 badge 画 */
|
||||
kind: string
|
||||
/** 编辑范围的起止行,0 起:一般是 header,库规则认领的块是原块整条语句(盖住它留下的子块,见 coveredByEditing) */
|
||||
/** 编辑范围的起止行,0 起:块的完整源码,头部连同各子孙块、结尾(块编辑时子孙都不画,见 coveredByEditing) */
|
||||
startLine: number
|
||||
endLine: number
|
||||
/** 编辑范围的源码,去掉共同缩进 */
|
||||
|
||||
Reference in new issue
Block a user