Add support for \subfile (#28517)

* Add \subfile to LaTeX grammar
* Handle \subfile in word count
* Show tooltip for \subfile in Visual Editor
* Add subfile completions

GitOrigin-RevId: d9c4308581bd3f25accb97f2191ffe1e5a64764e
This commit is contained in:
Alf Eaton
2025-09-17 11:22:38 +01:00
committed by Copybot
parent d2f5cd4e46
commit 141aca070a
9 changed files with 54 additions and 16 deletions

View File

@@ -14,6 +14,7 @@ import { RefTooltipContent } from './command-tooltip/ref-tooltip'
import { IncludeTooltipContent } from './command-tooltip/include-tooltip'
import { InputTooltipContent } from './command-tooltip/input-tooltip'
import { getTooltip } from '@codemirror/view'
import { SubfileTooltipContent } from '@/features/source-editor/components/command-tooltip/subfile-tooltip'
export const CodeMirrorCommandTooltip = memo(function CodeMirrorLinkTooltip() {
const view = useCodeMirrorViewContext()
@@ -86,6 +87,8 @@ const CodeMirrorCommandTooltipContent = memo<{
return <IncludeTooltipContent />
case 'Input':
return <InputTooltipContent />
case 'Subfile':
return <SubfileTooltipContent />
default:
return null
}

View File

@@ -0,0 +1,24 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useIncludedFile } from '@/features/source-editor/hooks/use-included-file'
import OLButton from '@/shared/components/ol/ol-button'
import MaterialIcon from '@/shared/components/material-icon'
export const SubfileTooltipContent: FC = () => {
const { t } = useTranslation()
const { openIncludedFile } = useIncludedFile('SubfileArgument')
return (
<div className="ol-cm-command-tooltip-content">
<OLButton
variant="link"
type="button"
className="ol-cm-command-tooltip-link"
onClick={openIncludedFile}
>
<MaterialIcon type="edit" />
{t('open_file')}
</OLButton>
</div>
)
}

View File

@@ -201,6 +201,16 @@ const createTooltipState = (
return buildTooltip(commandName, pos, value, commandNode, argumentNode)
}
// a subfile file (\subfile)
case 'Subfile': {
const argumentNode = commandNode
.getChild('SubfileArgument')
?.getChild(FilePathArgument)
?.getChild(LiteralArgContent)
return buildTooltip(commandName, pos, value, commandNode, argumentNode)
}
}
return null

View File

@@ -242,20 +242,7 @@ export const packageArgumentCompletionSource: CompletionSource =
export const inputArgumentCompletionSource: CompletionSource =
makeArgumentCompletionSource(
['InputArgument'],
({ completions, context, from }) => {
buildIncludeCompletions(completions, context)
return {
from,
validFor: /^[^}]*/,
options: completions.includes,
}
}
)
export const includeArgumentCompletionSource: CompletionSource =
makeArgumentCompletionSource(
['IncludeArgument'],
['InputArgument', 'IncludeArgument', 'SubfileArgument'],
({ completions, context, from }) => {
buildIncludeCompletions(completions, context)
@@ -355,7 +342,6 @@ export const argumentCompletionSources: CompletionSource[] = [
refArgumentCompletionSource,
packageArgumentCompletionSource,
inputArgumentCompletionSource,
includeArgumentCompletionSource,
includeGraphicsArgumentCompletionSource,
environmentNameCompletionSource,
documentClassArgumentCompletionSource,

View File

@@ -56,6 +56,14 @@ export function buildIncludeCompletions(
apply: `\\input{${removeTexExtension(path)}}`,
extend: extendOverUnpairedClosingBrace,
})
// \subfile{path}
completions.commands.push({
type: 'cmd',
label: `\\subfile{${path}}`,
apply: `\\subfile{${removeTexExtension(path)}}`,
extend: extendOverUnpairedClosingBrace,
})
}
// TODO: a better list of graphics extensions?

View File

@@ -51,6 +51,7 @@ const typeMap: Record<string, string[]> = {
HrefCommand: ['$CommandTooltipCommand'],
Include: ['$CommandTooltipCommand'],
Input: ['$CommandTooltipCommand'],
Subfile: ['$CommandTooltipCommand'],
Ref: ['$CommandTooltipCommand'],
UrlCommand: ['$CommandTooltipCommand'],
// text formatting commands that can be toggled via the toolbar

View File

@@ -78,6 +78,7 @@
SubParagraphCtrlSeq,
InputCtrlSeq,
IncludeCtrlSeq,
SubfileCtrlSeq,
ItemCtrlSeq,
NewTheoremCtrlSeq,
TheoremStyleCtrlSeq,
@@ -350,6 +351,9 @@ KnownCommand<ArgumentType> {
Include {
IncludeCtrlSeq IncludeArgument { FilePathArgument }
} |
Subfile {
SubfileCtrlSeq SubfileArgument { FilePathArgument }
} |
Centering {
CenteringCtrlSeq
} |

View File

@@ -65,6 +65,7 @@ import {
SubParagraphCtrlSeq,
InputCtrlSeq,
IncludeCtrlSeq,
SubfileCtrlSeq,
ItemCtrlSeq,
NewTheoremCtrlSeq,
TheoremStyleCtrlSeq,
@@ -580,6 +581,7 @@ const otherKnowncommands = {
'\\subparagraph': SubParagraphCtrlSeq,
'\\input': InputCtrlSeq,
'\\include': IncludeCtrlSeq,
'\\subfile': SubfileCtrlSeq,
'\\item': ItemCtrlSeq,
'\\centering': CenteringCtrlSeq,
'\\newtheorem': NewTheoremCtrlSeq,

View File

@@ -254,7 +254,7 @@ export const countWordsInFile = (
iterateNode(nodeRef, 'footnote')
return false
},
'IncludeArgument InputArgument'(nodeRef) {
'IncludeArgument InputArgument SubfileArgument'(nodeRef) {
const path = content.substring(nodeRef.from + 1, nodeRef.to - 1)
debugConsole.log(path)
if (path) {