mirror of
https://github.com/stablyai/orca.git
synced 2026-10-05 16:02:22 +00:00
perf(renderer): scope editor Git subscriptions by worktree (#8107)
This commit is contained in:
@@ -18,6 +18,10 @@ import { useEditorPanelContentState } from './useEditorPanelContentState'
|
||||
import { useMarkdownPreviewShortcut } from './useMarkdownPreviewShortcut'
|
||||
import { useUntitledFileRename } from './useUntitledFileRename'
|
||||
import { extractFrontMatter } from './markdown-frontmatter'
|
||||
import {
|
||||
selectEditorPanelGitBranchEntries,
|
||||
selectEditorPanelGitStatusEntries
|
||||
} from './editor-panel-git-entry-selector'
|
||||
|
||||
function EditorPanelInner({
|
||||
activeFileId: activeFileIdProp,
|
||||
@@ -33,10 +37,17 @@ function EditorPanelInner({
|
||||
const activeFileId = activeFileIdProp ?? globalActiveFileId
|
||||
const activeViewStateId = activeViewStateIdProp ?? activeFileId
|
||||
const activeFile = openFiles.find((f) => f.id === activeFileId) ?? null
|
||||
const activeWorktreeId = activeFile?.worktreeId
|
||||
const markFileDirty = useAppStore((s) => s.markFileDirty)
|
||||
const pendingEditorReveal = useAppStore((s) => s.pendingEditorReveal)
|
||||
const gitStatusByWorktree = useAppStore((s) => s.gitStatusByWorktree)
|
||||
const gitBranchChangesByWorktree = useAppStore((s) => s.gitBranchChangesByWorktree)
|
||||
// Why: background Git refreshes for other worktrees must not wake every
|
||||
// mounted Monaco/rich editor pane.
|
||||
const gitStatusEntries = useAppStore((s) =>
|
||||
selectEditorPanelGitStatusEntries(s, activeWorktreeId)
|
||||
)
|
||||
const gitBranchEntries = useAppStore((s) =>
|
||||
selectEditorPanelGitBranchEntries(s, activeWorktreeId)
|
||||
)
|
||||
const markdownViewMode = useAppStore((s) => s.markdownViewMode)
|
||||
const setMarkdownViewMode = useAppStore((s) => s.setMarkdownViewMode)
|
||||
const editorViewMode = useAppStore((s) => s.editorViewMode)
|
||||
@@ -96,7 +107,7 @@ function EditorPanelInner({
|
||||
activeFile,
|
||||
isChangesMode: requestedChangesMode,
|
||||
openFiles,
|
||||
gitStatusByWorktree,
|
||||
gitStatusEntries,
|
||||
editorViewMode
|
||||
})
|
||||
const isChangesMode =
|
||||
@@ -225,8 +236,8 @@ function EditorPanelInner({
|
||||
activeFile,
|
||||
fileContents,
|
||||
editorDrafts,
|
||||
gitStatusByWorktree,
|
||||
gitBranchChangesByWorktree,
|
||||
gitStatusEntries,
|
||||
gitBranchEntries,
|
||||
markdownViewMode,
|
||||
isChangesMode
|
||||
})
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { GitBranchChangeEntry, GitStatusEntry } from '../../../../shared/types'
|
||||
import {
|
||||
selectEditorPanelGitBranchEntries,
|
||||
selectEditorPanelGitStatusEntries
|
||||
} from './editor-panel-git-entry-selector'
|
||||
|
||||
describe('editor panel Git entry selectors', () => {
|
||||
it('ignore background worktree writes across mounted editor panels', () => {
|
||||
const panelCount = 200
|
||||
const worktreeId = 'worktree-active'
|
||||
const statusEntries = [{ path: 'src/index.ts' }] as GitStatusEntry[]
|
||||
const branchEntries = [{ path: 'src/index.ts' }] as GitBranchChangeEntry[]
|
||||
let gitStatusByWorktree = { [worktreeId]: statusEntries }
|
||||
let gitBranchChangesByWorktree = { [worktreeId]: branchEntries }
|
||||
let wholeMapInvalidations = 0
|
||||
let scopedEntryInvalidations = 0
|
||||
|
||||
for (let write = 0; write < 200; write += 1) {
|
||||
const previousStatusMap = gitStatusByWorktree
|
||||
gitStatusByWorktree = {
|
||||
...gitStatusByWorktree,
|
||||
[`background-${write}`]: [{ path: `generated-${write}.ts` } as GitStatusEntry]
|
||||
}
|
||||
const previousBranchMap = gitBranchChangesByWorktree
|
||||
gitBranchChangesByWorktree = {
|
||||
...gitBranchChangesByWorktree,
|
||||
[`background-${write}`]: [{ path: `generated-${write}.ts` } as GitBranchChangeEntry]
|
||||
}
|
||||
|
||||
for (let panel = 0; panel < panelCount; panel += 1) {
|
||||
wholeMapInvalidations += Number(previousStatusMap !== gitStatusByWorktree)
|
||||
wholeMapInvalidations += Number(previousBranchMap !== gitBranchChangesByWorktree)
|
||||
scopedEntryInvalidations += Number(
|
||||
selectEditorPanelGitStatusEntries({ gitStatusByWorktree }, worktreeId) !== statusEntries
|
||||
)
|
||||
scopedEntryInvalidations += Number(
|
||||
selectEditorPanelGitBranchEntries({ gitBranchChangesByWorktree }, worktreeId) !==
|
||||
branchEntries
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
expect(wholeMapInvalidations).toBe(80_000)
|
||||
expect(scopedEntryInvalidations).toBe(0)
|
||||
})
|
||||
|
||||
it('publishes owning entry replacements and handles an absent worktree', () => {
|
||||
const firstStatus = [{ path: 'src/old.ts' }] as GitStatusEntry[]
|
||||
const nextStatus = [{ path: 'src/new.ts' }] as GitStatusEntry[]
|
||||
const firstBranch = [{ path: 'src/old.ts' }] as GitBranchChangeEntry[]
|
||||
const nextBranch = [{ path: 'src/new.ts' }] as GitBranchChangeEntry[]
|
||||
|
||||
expect(
|
||||
selectEditorPanelGitStatusEntries({ gitStatusByWorktree: { active: firstStatus } }, 'active')
|
||||
).toBe(firstStatus)
|
||||
expect(
|
||||
selectEditorPanelGitStatusEntries({ gitStatusByWorktree: { active: nextStatus } }, 'active')
|
||||
).toBe(nextStatus)
|
||||
expect(
|
||||
selectEditorPanelGitBranchEntries(
|
||||
{ gitBranchChangesByWorktree: { active: firstBranch } },
|
||||
'active'
|
||||
)
|
||||
).toBe(firstBranch)
|
||||
expect(
|
||||
selectEditorPanelGitBranchEntries(
|
||||
{ gitBranchChangesByWorktree: { active: nextBranch } },
|
||||
'active'
|
||||
)
|
||||
).toBe(nextBranch)
|
||||
expect(selectEditorPanelGitStatusEntries({ gitStatusByWorktree: {} }, null)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { AppState } from '@/store'
|
||||
|
||||
type EditorPanelGitEntryState = Pick<AppState, 'gitBranchChangesByWorktree' | 'gitStatusByWorktree'>
|
||||
|
||||
export function selectEditorPanelGitStatusEntries(
|
||||
state: Pick<EditorPanelGitEntryState, 'gitStatusByWorktree'>,
|
||||
worktreeId: string | null | undefined
|
||||
): AppState['gitStatusByWorktree'][string] | undefined {
|
||||
return worktreeId ? state.gitStatusByWorktree[worktreeId] : undefined
|
||||
}
|
||||
|
||||
export function selectEditorPanelGitBranchEntries(
|
||||
state: Pick<EditorPanelGitEntryState, 'gitBranchChangesByWorktree'>,
|
||||
worktreeId: string | null | undefined
|
||||
): AppState['gitBranchChangesByWorktree'][string] | undefined {
|
||||
return worktreeId ? state.gitBranchChangesByWorktree[worktreeId] : undefined
|
||||
}
|
||||
@@ -34,12 +34,13 @@ function renderModel(args: {
|
||||
isChangesMode?: boolean
|
||||
gitStatusByWorktree?: Record<string, GitStatusEntry[]>
|
||||
}) {
|
||||
const activeFile = args.activeFile ?? markdownFile()
|
||||
return getEditorPanelRenderModel({
|
||||
activeFile: args.activeFile ?? markdownFile(),
|
||||
activeFile,
|
||||
fileContents: args.fileContents ?? { '/repo/README.md': textContent() },
|
||||
editorDrafts: args.editorDrafts ?? {},
|
||||
gitStatusByWorktree: args.gitStatusByWorktree ?? {},
|
||||
gitBranchChangesByWorktree: {},
|
||||
gitStatusEntries: args.gitStatusByWorktree?.[activeFile.worktreeId],
|
||||
gitBranchEntries: undefined,
|
||||
markdownViewMode: args.markdownViewMode ?? {},
|
||||
isChangesMode: args.isChangesMode ?? false
|
||||
})
|
||||
|
||||
@@ -22,8 +22,8 @@ type EditorPanelRenderModelParams = {
|
||||
activeFile: OpenFile
|
||||
fileContents: Record<string, FileContent>
|
||||
editorDrafts: StoreState['editorDrafts']
|
||||
gitStatusByWorktree: StoreState['gitStatusByWorktree']
|
||||
gitBranchChangesByWorktree: StoreState['gitBranchChangesByWorktree']
|
||||
gitStatusEntries: StoreState['gitStatusByWorktree'][string] | undefined
|
||||
gitBranchEntries: StoreState['gitBranchChangesByWorktree'][string] | undefined
|
||||
markdownViewMode: StoreState['markdownViewMode']
|
||||
isChangesMode: boolean
|
||||
}
|
||||
@@ -32,8 +32,8 @@ export function getEditorPanelRenderModel({
|
||||
activeFile,
|
||||
fileContents,
|
||||
editorDrafts,
|
||||
gitStatusByWorktree,
|
||||
gitBranchChangesByWorktree,
|
||||
gitStatusEntries,
|
||||
gitBranchEntries,
|
||||
markdownViewMode,
|
||||
isChangesMode
|
||||
}: EditorPanelRenderModelParams) {
|
||||
@@ -54,8 +54,8 @@ export function getEditorPanelRenderModel({
|
||||
activeFile.mode === 'diff'
|
||||
? detectLanguage(activeFile.relativePath)
|
||||
: detectLanguage(activeFile.filePath)
|
||||
const worktreeEntries = gitStatusByWorktree[activeFile.worktreeId] ?? []
|
||||
const branchEntries = gitBranchChangesByWorktree[activeFile.worktreeId] ?? []
|
||||
const worktreeEntries = gitStatusEntries ?? []
|
||||
const branchEntries = gitBranchEntries ?? []
|
||||
const matchingWorktreeEntry =
|
||||
activeFile.mode === 'diff' &&
|
||||
(activeFile.diffSource === 'staged' || activeFile.diffSource === 'unstaged')
|
||||
|
||||
@@ -101,7 +101,7 @@ function HookProbe({
|
||||
activeFile,
|
||||
isChangesMode: false,
|
||||
openFiles,
|
||||
gitStatusByWorktree,
|
||||
gitStatusEntries: activeFile ? gitStatusByWorktree[activeFile.worktreeId] : undefined,
|
||||
editorViewMode: {}
|
||||
})
|
||||
latestFileContents = state.fileContents
|
||||
|
||||
@@ -41,7 +41,7 @@ type UseEditorPanelContentStateParams = {
|
||||
activeFile: OpenFile | null
|
||||
isChangesMode: boolean
|
||||
openFiles: OpenFile[]
|
||||
gitStatusByWorktree: GitStatusByWorktree
|
||||
gitStatusEntries: GitStatusByWorktree[string] | undefined
|
||||
editorViewMode: EditorViewModeByFile
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function useEditorPanelContentState({
|
||||
activeFile,
|
||||
isChangesMode,
|
||||
openFiles,
|
||||
gitStatusByWorktree,
|
||||
gitStatusEntries,
|
||||
editorViewMode
|
||||
}: UseEditorPanelContentStateParams): UseEditorPanelContentStateResult {
|
||||
const [fileContents, setFileContents] = useState<Record<string, FileContent>>({})
|
||||
@@ -362,7 +362,7 @@ export function useEditorPanelContentState({
|
||||
}
|
||||
|
||||
const snapshotPaths = new Set(snapshotEntries.map((entry) => entry.path))
|
||||
const liveEntries = gitStatusByWorktree[activeFile.worktreeId] ?? []
|
||||
const liveEntries = gitStatusEntries ?? []
|
||||
for (const entry of liveEntries) {
|
||||
if (
|
||||
!snapshotPaths.has(entry.path) ||
|
||||
@@ -411,7 +411,7 @@ export function useEditorPanelContentState({
|
||||
activeFile?.conflictReview?.snapshotTimestamp,
|
||||
selectedConflictReviewFile?.id,
|
||||
isChangesMode,
|
||||
gitStatusByWorktree
|
||||
gitStatusEntries
|
||||
])
|
||||
|
||||
useEditorPanelFileLoadRetry({
|
||||
@@ -423,9 +423,7 @@ export function useEditorPanelContentState({
|
||||
setFileContents
|
||||
})
|
||||
|
||||
const changesStatusEntries = activeFile?.worktreeId
|
||||
? gitStatusByWorktree[activeFile.worktreeId]
|
||||
: undefined
|
||||
const changesStatusEntries = activeFile?.worktreeId ? gitStatusEntries : undefined
|
||||
const activeFileGitStatusEntries = useMemo(() => {
|
||||
if (!activeFile?.relativePath || !changesStatusEntries) {
|
||||
return undefined
|
||||
|
||||
Reference in New Issue
Block a user