[web] add a hook for discovering the current root doc (#34027)

GitOrigin-RevId: d1930e6b13ca18dbae927dc15a5c6507351f71c8
This commit is contained in:
Jakob Ackermann
2026-05-28 08:06:08 +00:00
committed by Copybot
parent 14b04ad4b8
commit 984b8e3f4a
3 changed files with 49 additions and 45 deletions
@@ -1,4 +1,3 @@
import { isMainFile } from './editor-files'
import getMeta from '../../../utils/meta'
import { deleteJSON, postJSON } from '../../../infrastructure/fetch-json'
import { debounce, DebouncedFunc } from 'lodash'
@@ -8,9 +7,9 @@ import { debugConsole } from '@/utils/debugging'
import { signalWithTimeout } from '@/utils/abort-signal'
import { Dispatch, SetStateAction } from 'react'
import { OpenDocuments } from '@/features/ide-react/editor/open-documents'
import { DocumentContainer } from '@/features/ide-react/editor/document-container'
import { CompileOptions, CompileResponseData } from '@ol-types/compile'
import { DeliveryLatencies } from './types'
import { RootDocInfo } from '@/shared/hooks/use-root-doc'
const AUTO_COMPILE_MAX_WAIT = 5000
// We add a 2 second debounce to sending user changes to server if they aren't
@@ -36,14 +35,12 @@ export default class DocumentCompiler {
cleanupCompileResult: () => void
signal: AbortSignal
openDocs: OpenDocuments
projectRootDocId: string | null
clsiServerId: string | null
currentDoc: DocumentContainer | null
error: Error | undefined
timer: number
defaultOptions: CompileOptions
debouncedAutoCompile: DebouncedFunc<() => void>
pathInFolder: (docId: string) => string | null
getRootDocInfo: () => RootDocInfo
constructor({
compilingRef,
@@ -57,6 +54,7 @@ export default class DocumentCompiler {
cleanupCompileResult,
signal,
openDocs,
getRootDocInfo,
}: {
compilingRef: React.MutableRefObject<boolean>
projectId: string
@@ -69,6 +67,7 @@ export default class DocumentCompiler {
cleanupCompileResult: () => void
signal: AbortSignal
openDocs: OpenDocuments
getRootDocInfo: () => RootDocInfo
}) {
this.compilingRef = compilingRef
this.projectId = projectId
@@ -81,11 +80,9 @@ export default class DocumentCompiler {
this.cleanupCompileResult = cleanupCompileResult
this.signal = signal
this.openDocs = openDocs
this.pathInFolder = () => null
this.getRootDocInfo = getRootDocInfo
this.projectRootDocId = null
this.clsiServerId = null
this.currentDoc = null
this.error = undefined
this.timer = 0
this.defaultOptions = {
@@ -136,9 +133,7 @@ export default class DocumentCompiler {
const t0 = performance.now()
const rootDocId = this.getRootDocOverrideId() || this.projectRootDocId
const rootResourcePath =
(rootDocId && this.pathInFolder(rootDocId)) || 'main.tex'
const { rootDocId, rootResourcePath } = this.getRootDocInfo()
const body = {
rootDoc_id: rootDocId,
@@ -184,21 +179,6 @@ export default class DocumentCompiler {
}
}
// parse the text of the current doc in the editor
// if it contains "\documentclass" then use this as the root doc
getRootDocOverrideId() {
// only override when not in the root doc itself
if (this.currentDoc && this.currentDoc.doc_id !== this.projectRootDocId) {
const snapshot = this.currentDoc.getSnapshot()
if (snapshot && isMainFile(snapshot)) {
return this.currentDoc.doc_id
}
}
return null
}
// build the query parameters added to post-compile requests
buildPostCompileParams() {
const params = new URLSearchParams()
@@ -34,6 +34,7 @@ import { useUserContext } from './user-context'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { useDetachContext } from '@/shared/context/detach-context'
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
import { useRootDoc } from '@/shared/hooks/use-root-doc'
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
@@ -151,7 +152,8 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
const { features, alphaProgram } = useUserContext()
const { fileTreeData } = useFileTreeData()
const { pathInFolder, findEntityByPath } = useFileTreePathContext()
const { findEntityByPath } = useFileTreePathContext()
const getRootDocInfo = useRootDoc()
// whether a compile is in progress
const [compiling, setCompiling] = useState(false)
@@ -332,22 +334,14 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
compilingRef,
signal,
openDocs,
getRootDocInfo,
})
})
// keep currentDoc in sync with the compiler
// keep the root doc lookup in sync with the compiler
useEffect(() => {
compiler.currentDoc = currentDocument
}, [compiler, currentDocument])
// keep the project rootDocId in sync with the compiler
useEffect(() => {
compiler.projectRootDocId = rootDocId || null
}, [compiler, rootDocId])
// keep pathInFolder in sync with the compiler
useEffect(() => {
compiler.pathInFolder = pathInFolder
}, [compiler, pathInFolder])
compiler.getRootDocInfo = getRootDocInfo
}, [compiler, getRootDocInfo])
// keep draft setting in sync with the compiler
useEffect(() => {
@@ -399,9 +393,8 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
dataFromCache.rootDocId = findEntityByPath(
dataFromCache.options?.rootResourcePath || ''
)?.entity?._id
const rootDocOverride = compiler.getRootDocOverrideId() || rootDocId
settingsUpToDate =
rootDocOverride === dataFromCache.rootDocId &&
getRootDocInfo().rootDocId === dataFromCache.rootDocId &&
dataFromCache.options.draft === draft &&
// Allow stopOnFirstError to be enabled in the compile from cache and disabled locally.
// Compiles that passed with stopOnFirstError=true will also pass with stopOnFirstError=false. The inverse does not hold, and we need to recompile.
@@ -428,9 +421,8 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
joinedOnce,
currentDocument,
compiledOnce,
rootDocId,
findEntityByPath,
compiler,
getRootDocInfo,
compilerName,
imageName,
stopOnFirstError,
@@ -552,7 +544,7 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
result.logEntries.all
) as Record<string, number>
const rootDocId = data.rootDocId || compiler.projectRootDocId
const rootDocId = data.rootDocId
const previousRuleCounts = previousRuleCountsRef.current
previousRuleCountsRef.current = { ruleCounts, rootDocId }
@@ -654,7 +646,6 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
setLogEntries,
setLogEntryAnnotations,
setPdfFile,
compiler,
])
// switch to logs if there's an error
@@ -0,0 +1,33 @@
import { useCallback } from 'react'
import { useProjectContext } from '@/shared/context/project-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
import { isMainFile } from '@/features/pdf-preview/util/editor-files'
export type RootDocInfo = {
rootDocId: string | null
rootResourcePath: string
}
export function useRootDoc(): () => RootDocInfo {
const { project } = useProjectContext()
const projectRootDocId = project?.rootDocId ?? null
const { currentDocument } = useEditorOpenDocContext()
const { pathInFolder } = useFileTreePathContext()
// Inspecting the document content is too expensive to perform on every update.
// Compute the rootDoc on-demand instead.
return useCallback((): RootDocInfo => {
let rootDocId: string | null = projectRootDocId
if (
currentDocument &&
currentDocument.doc_id !== projectRootDocId &&
isMainFile(currentDocument.getSnapshot())
) {
rootDocId = currentDocument.doc_id
}
const rootResourcePath =
(rootDocId && pathInFolder(rootDocId)) || 'main.tex'
return { rootDocId, rootResourcePath }
}, [projectRootDocId, currentDocument, pathInFolder])
}