mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> GitOrigin-RevId: 5fcaef2004495a5e71bc083cf8fdc2bffce579d5
95 lines
3.6 KiB
TypeScript
95 lines
3.6 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { EditorState } from '@codemirror/state'
|
|
import { SyntaxNode } from '@lezer/common'
|
|
import { syntaxTree } from '@codemirror/language'
|
|
import { useProjectContext } from '@/shared/context/project-context'
|
|
import { debugConsole } from '@/utils/debugging'
|
|
import { CommandDefinition } from '../utils/command-definitions/command-definition'
|
|
import { getCommandDefinitionIndexer } from '../utils/command-definitions/command-definition-indexer'
|
|
import { useCodeMirrorStateContext } from '../components/codemirror-context'
|
|
import { contextMenuStateField } from '../extensions/context-menu'
|
|
|
|
/**
|
|
* Look up where `name` is defined in the project, by parsing the snapshot in a
|
|
* worker. Intended to be used while the editor context menu is open: the lookup
|
|
* runs off the main thread, so opening the menu is never blocked, and the item
|
|
* is disabled until the worker responds.
|
|
*
|
|
* Pass `name` null when the menu was not opened on a command, to avoid
|
|
* refreshing the snapshot (a project-history flush) and parsing on every
|
|
* right-click. `lookupKey` should change on each open (e.g. the tooltip object)
|
|
* so the lookup reruns even if the menu component stays mounted across reopens.
|
|
*/
|
|
function useCommandDefinition(name: string | null, lookupKey: unknown) {
|
|
const { projectSnapshot } = useProjectContext()
|
|
|
|
const [definition, setDefinition] = useState<CommandDefinition | null>(null)
|
|
|
|
useEffect(() => {
|
|
// Reset while the new lookup runs so a stale definition isn't shown.
|
|
setDefinition(null)
|
|
if (!name) {
|
|
return
|
|
}
|
|
let cancelled = false
|
|
;(async () => {
|
|
try {
|
|
await projectSnapshot.refresh()
|
|
if (cancelled) {
|
|
return
|
|
}
|
|
const found = await getCommandDefinitionIndexer().findDefinition(
|
|
projectSnapshot,
|
|
name
|
|
)
|
|
if (!cancelled) {
|
|
setDefinition(found)
|
|
}
|
|
} catch (error) {
|
|
debugConsole.error('Failed to find command definition', error)
|
|
}
|
|
})()
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [projectSnapshot, name, lookupKey])
|
|
|
|
return definition
|
|
}
|
|
|
|
/**
|
|
* Resolve the command the editor context menu was opened on and its project-wide
|
|
* definition (if any). Returns nulls when the menu was not opened on a command;
|
|
* the lookup only runs when the menu was opened on a command.
|
|
*/
|
|
export function useContextMenuCommandDefinition() {
|
|
const state = useCodeMirrorStateContext()
|
|
// The tooltip is a new object on each open, so use it as the lookup key.
|
|
const tooltip = state.field(contextMenuStateField, false)?.tooltip ?? null
|
|
const pos = tooltip?.pos ?? null
|
|
const commandName = pos != null ? commandNameAtPos(state, pos) : null
|
|
const commandDefinition = useCommandDefinition(commandName, tooltip)
|
|
return { commandName, commandDefinition }
|
|
}
|
|
|
|
/**
|
|
* Resolve the control sequence (e.g. `\mycmd`) at a document position, or null
|
|
* if the position is not on a command. Walks up from the innermost node to the
|
|
* nearest node carrying a control-sequence token, mirroring the command-usage
|
|
* logic in tree-operations/commands.ts.
|
|
*/
|
|
function commandNameAtPos(state: EditorState, pos: number): string | null {
|
|
const tree = syntaxTree(state)
|
|
// Bias right (as resolveCommandNode does) so a click on the command's first
|
|
// character resolves into it rather than the preceding token.
|
|
let node: SyntaxNode | null = tree.resolveInner(pos, 1)
|
|
for (; node; node = node.parent) {
|
|
const ctrlSeq = node.getChild('$CtrlSeq')
|
|
if (ctrlSeq && !ctrlSeq.type.is('$CtrlSym')) {
|
|
const name = state.sliceDoc(ctrlSeq.from, ctrlSeq.to)
|
|
return name.length > 0 ? name : null
|
|
}
|
|
}
|
|
return null
|
|
}
|