✨ feat(vscode): 代码阅读侧栏拆成多栏:常驻未提交的更改、收藏目录各占一栏
This commit is contained in:
1 parent
45e526afa9
commit
b40ddb5e3a
27 files changed
+2427
-674
No files matched your search
@@ -1,15 +1,17 @@
|
||||
import type { Disposable, ExtensionContext, QuickPickItem, TreeView, WorkspaceFolder } from 'vscode'
|
||||
import type { Disposable, Event, ExtensionContext, QuickPickItem, TreeView, WorkspaceFolder } from 'vscode'
|
||||
import type { PrFileDto } from '../../gitea/api'
|
||||
import type { RepoContext } from '../../gitea/repoContext'
|
||||
import type { Issue } from '../../gitea/types'
|
||||
import type { DiffBase } from '../diff/diffBase'
|
||||
import type { DiffBase, DiffBaseSource } from '../diff/diffBase'
|
||||
import type { FileTreeProvider, TreeNode } from '../explorer/fileTree'
|
||||
import type { TreeFilter, TreeFilters } from '../explorer/treeFilters'
|
||||
import type { FileCount } from './pickList'
|
||||
import * as path from 'node:path'
|
||||
import { QuickPickItemKind, Uri, window, workspace } from 'vscode'
|
||||
import { commands, EventEmitter, QuickPickItemKind, Uri, window, workspace } from 'vscode'
|
||||
import { fileAtRef } from '../../git/fileAt'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { WORKSPACE_TREE } from '../explorer/fileTree'
|
||||
import { TestsToggle } from '../explorer/testsToggle'
|
||||
import { whenShown } from '../explorer/viewShown'
|
||||
import { toPathKey } from '../pathMap'
|
||||
import { ChangeDecorations } from './changeDecorations'
|
||||
import { changeOf, reviewBaseFile, reviewTree } from './changedFiles'
|
||||
@@ -20,6 +22,10 @@ import { localWorktree, prepareWorktree } from './reviewWorktree'
|
||||
|
||||
/** workspaceState:正在审查的工单号,重开窗口接着审查。 */
|
||||
const REVIEW_ISSUE_KEY = 'superpowers.codeReader.reviewIssue'
|
||||
/** 上下文键:主栏正审查着工单。标题栏的「显示全部」、隐藏 / 显示测试文件按它显示,主栏行上的注释、收藏按钮按它藏起来(那些行是工作副本里的)。 */
|
||||
const REVIEWING_CONTEXT = 'superpowers.codeReader.reviewing'
|
||||
/** 审查时隐藏测试文件的开关:记在 workspaceState 的键、标题栏按它换按钮的上下文键 */
|
||||
const REVIEW_TESTS = { state: 'superpowers.codeReader.reviewHideTests', context: 'superpowers.codeReader.reviewTestsHidden' }
|
||||
|
||||
/**
|
||||
* 怎么进的审查,决定不能审查、出错时怎么收场:
|
||||
@@ -49,34 +55,43 @@ function errorText(err: unknown): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* 侧栏按审查工单筛文件:「选工单」列出看板「审查」「进行中」列里能审查的工单,选中后树里只剩这张工单的 PR 改过的文件(按改动着色),
|
||||
* 点开的是本机这张工单的工作副本里 PR 最新的代码,预览对比 PR 的目标分支(diffBase);「显示全部」回到工作区文件树。选的工单按工作区记住。
|
||||
* 和别的筛选(未提交的更改)互斥,见 TreeFilters。
|
||||
* 主栏按审查工单筛文件:「选工单」列出看板「审查」「进行中」列里能审查的工单,选中后主栏只剩这张工单的 PR 改过的文件(按改动着色),
|
||||
* 点开的是本机这张工单的工作副本里 PR 最新的代码,工作副本里的文件预览对比 PR 的目标分支(diffBase);「显示全部」回到工作区文件树。
|
||||
* 选的工单按工作区记住,主栏第一次露出来时接着审查;隐藏测试文件的开关是审查自己的,也按工作区记住。
|
||||
*/
|
||||
export class ReviewMode implements TreeFilter, Disposable {
|
||||
export class ReviewMode implements DiffBaseSource, Disposable {
|
||||
private readonly decorations = new ChangeDecorations()
|
||||
private readonly baseChange = new EventEmitter<void>()
|
||||
/** 审查的对比基准变了(进了审查、重读了 PR、换了工单、退出了):打开着的预览重新问 */
|
||||
readonly onDidChange: Event<void> = this.baseChange.event
|
||||
private readonly tests: TestsToggle
|
||||
private readonly disposables: Disposable[]
|
||||
/** 正在审查的:工单、PR、改过的文件、工作副本、对比基准 */
|
||||
/** 正在审查的(主栏摆着它):工单、PR、改过的文件、工作副本、对比基准 */
|
||||
private loaded: Loaded | undefined
|
||||
/** 每开始一次进审查、退出审查都加一:慢的那次回来时已经不是最新的,结果丢掉。 */
|
||||
private generation = 0
|
||||
|
||||
/** viewId 是侧栏视图的 id:进审查时在这个视图上转进度。 */
|
||||
/** viewId、view、files 是主栏的视图 id、视图、文件树:进审查时在主栏上转进度、换上 PR 改过的文件。 */
|
||||
constructor(
|
||||
private readonly context: ExtensionContext,
|
||||
private readonly viewId: string,
|
||||
private readonly view: TreeView<TreeNode>,
|
||||
private readonly files: FileTreeProvider,
|
||||
private readonly filters: TreeFilters,
|
||||
) {
|
||||
filters.add(this)
|
||||
this.tests = new TestsToggle(context.workspaceState, REVIEW_TESTS, () => this.place())
|
||||
this.disposables = [
|
||||
this.decorations,
|
||||
this.baseChange,
|
||||
window.registerFileDecorationProvider(this.decorations),
|
||||
filters.whenShown(() => void this.restore()),
|
||||
whenShown(view, () => void this.restore()),
|
||||
]
|
||||
}
|
||||
|
||||
/** 正审查着工单:主栏只摆着 PR 改过的文件 */
|
||||
get reviewing(): boolean {
|
||||
return this.loaded !== undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* 「选工单」:顶部弹出快速选择,列出看板上现在摆着的「审查」「进行中」列里能审查的工单(按列分组),马上就有;
|
||||
* 各 PR 改了几个文件一起去读,读到一个换一项,都读完才停止转圈。
|
||||
@@ -152,13 +167,20 @@ export class ReviewMode implements TreeFilter, Disposable {
|
||||
: []))
|
||||
}
|
||||
|
||||
/** 「显示全部」或换成别的筛选:不再审查、不再记着这张工单,正在进的审查作废;正审查着的回到工作区文件树。 */
|
||||
/** 「显示全部」:不再审查、不再记着这张工单,正在进的审查作废;正审查着的主栏回到工作区文件树,清掉标题后面的字和标题下面的一行。 */
|
||||
async exit(): Promise<void> {
|
||||
this.generation++
|
||||
const shown = this.loaded !== undefined
|
||||
this.loaded = undefined
|
||||
this.decorations.show([])
|
||||
if (shown) {
|
||||
this.baseChange.fire()
|
||||
this.files.show(WORKSPACE_TREE)
|
||||
this.view.description = undefined
|
||||
this.view.message = undefined
|
||||
}
|
||||
await Promise.all([
|
||||
this.filters.leave(this),
|
||||
commands.executeCommand('setContext', REVIEWING_CONTEXT, false),
|
||||
this.context.workspaceState.update(REVIEW_ISSUE_KEY, undefined),
|
||||
])
|
||||
}
|
||||
@@ -188,9 +210,9 @@ export class ReviewMode implements TreeFilter, Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
/** 隐藏测试文件的开关换了:正审查着的文件按新开关重摆。 */
|
||||
testsToggled(): void {
|
||||
this.place()
|
||||
/** 审查时标题栏的「隐藏 / 显示测试文件」:正审查着的文件按新开关重摆。 */
|
||||
setTestsHidden(hidden: boolean): Promise<void> {
|
||||
return this.tests.set(hidden)
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -277,13 +299,13 @@ export class ReviewMode implements TreeFilter, Disposable {
|
||||
|
||||
private async show(loaded: Loaded): Promise<void> {
|
||||
const root = Uri.file(loaded.worktree)
|
||||
// 先记下再进:进的时候打开着的预览就来问对比基准
|
||||
// 先记下再通知:打开着的预览马上来问对比基准
|
||||
this.loaded = loaded
|
||||
const entering = this.filters.enter(this, 'review')
|
||||
this.baseChange.fire()
|
||||
this.place()
|
||||
this.decorations.show(loaded.files.map(file => ({ uri: Uri.joinPath(root, file.path), change: changeOf(file.status) })))
|
||||
await Promise.all([
|
||||
entering,
|
||||
commands.executeCommand('setContext', REVIEWING_CONTEXT, true),
|
||||
this.context.workspaceState.update(REVIEW_ISSUE_KEY, loaded.issue.number),
|
||||
])
|
||||
}
|
||||
@@ -296,8 +318,8 @@ export class ReviewMode implements TreeFilter, Disposable {
|
||||
const { issue, pr, files, worktree } = loaded
|
||||
// 工作副本当根,名字照写工作区文件夹的:看的还是这个仓库,只是 PR 最新的代码
|
||||
const folder: WorkspaceFolder = { uri: Uri.file(worktree), name: workspace.workspaceFolders?.[0]?.name ?? path.basename(worktree), index: 0 }
|
||||
const { tree, hidden } = reviewTree(files, this.filters.testsHidden)
|
||||
this.files.showChanges([{ roots: [{ folder, tree }] }])
|
||||
const { tree, hidden } = reviewTree(files, this.tests.hidden)
|
||||
this.files.show({ kind: 'changes', view: [{ roots: [{ folder, tree }] }] })
|
||||
this.view.description = reviewDescription(issue.number, hidden)
|
||||
this.view.message = reviewMessage(issue, pr, files.length)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user