Просмотр исходного кода

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
Alf Eaton 11 месяцев назад
Родитель
Сommit
141aca070a

+ 3 - 0
services/web/frontend/js/features/source-editor/components/codemirror-command-tooltip.tsx

@@ -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
   }

+ 24 - 0
services/web/frontend/js/features/source-editor/components/command-tooltip/subfile-tooltip.tsx

@@ -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>
+  )
+}

+ 10 - 0
services/web/frontend/js/features/source-editor/extensions/command-tooltip.ts

@@ -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

+ 1 - 15
services/web/frontend/js/features/source-editor/languages/latex/complete.ts

@@ -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,

+ 8 - 0
services/web/frontend/js/features/source-editor/languages/latex/completions/include.ts

@@ -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?

+ 1 - 0
services/web/frontend/js/features/source-editor/languages/latex/latex-language.ts

@@ -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

+ 4 - 0
services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar

@@ -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
     } |

+ 2 - 0
services/web/frontend/js/features/source-editor/lezer-latex/tokens.mjs

@@ -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,

+ 1 - 1
services/web/frontend/js/features/word-count-modal/utils/count-words-in-file.ts

@@ -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) {