Procházet zdrojové kódy

[cm6] Add LineBreak to LaTeX grammar, highlight and decorate (#13742)

GitOrigin-RevId: 64c54676e0284b7187678eedcc5096b1aa284cc0
Alf Eaton před 3 roky
rodič
revize
f8f56cea89

+ 8 - 0
services/web/frontend/js/features/source-editor/extensions/visual/atomic-decorations.ts

@@ -57,6 +57,7 @@ import { getListItems } from '../toolbar/lists'
 import { TildeWidget } from './visual-widgets/tilde'
 import { BeginTheoremWidget } from './visual-widgets/begin-theorem'
 import { parseTheoremArguments } from '../../utils/tree-operations/theorems'
+import { IndicatorWidget } from './visual-widgets/indicator'
 
 type Options = {
   fileTreeManager: {
@@ -730,6 +731,13 @@ export const atomicDecorations = (options: Options) => {
               }).range(nodeRef.from, nodeRef.to)
             )
           }
+        } else if (nodeRef.type.is('LineBreakCtrlSym')) {
+          // line break
+          decorations.push(
+            Decoration.replace({
+              widget: new IndicatorWidget('\u21A9'),
+            }).range(nodeRef.from, nodeRef.to)
+          )
         } else if (nodeRef.type.is('Caption')) {
           if (shouldDecorate(state, nodeRef)) {
             // a caption

+ 3 - 10
services/web/frontend/js/features/source-editor/extensions/visual/utils/typeset-content.ts

@@ -1,14 +1,7 @@
 import { EditorState } from '@codemirror/state'
-import { SyntaxNode, SyntaxNodeRef } from '@lezer/common'
+import { SyntaxNode } from '@lezer/common'
 import { isUnknownCommandWithName } from '../../../utils/tree-query'
-
-function isNewline(node: SyntaxNodeRef, state: EditorState) {
-  if (!node.type.is('CtrlSym')) {
-    return false
-  }
-  const command = state.sliceDoc(node.from, node.to)
-  return command === '\\\\'
-}
+import { LineBreakCtrlSym } from '../../../lezer-latex/latex.terms.mjs'
 
 /**
  * Does a small amount of typesetting of LaTeX content into a DOM element.
@@ -78,7 +71,7 @@ export function typesetNodeIntoElement(
         // ignoring these commands
         from = childNode.to
         return false
-      } else if (isNewline(childNode, state)) {
+      } else if (childNode.type.is(LineBreakCtrlSym)) {
         ancestor().appendChild(document.createElement('br'))
         from = childNode.to
       }

+ 3 - 0
services/web/frontend/js/features/source-editor/extensions/visual/visual-theme.ts

@@ -104,6 +104,9 @@ export const visualTheme = EditorView.theme({
     filter: 'grayscale(1)',
     marginRight: '2px',
   },
+  '.ol-cm-indicator': {
+    color: 'rgba(125, 125, 125, 0.5)',
+  },
   '.ol-cm-begin': {
     fontFamily: 'var(--source-font-family)',
     minHeight: '1em',

+ 27 - 0
services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/indicator.ts

@@ -0,0 +1,27 @@
+import { WidgetType } from '@codemirror/view'
+
+export class IndicatorWidget extends WidgetType {
+  constructor(public content: string) {
+    super()
+  }
+
+  toDOM() {
+    const element = document.createElement('span')
+    element.classList.add('ol-cm-indicator')
+    element.textContent = this.content
+    return element
+  }
+
+  eq(widget: IndicatorWidget) {
+    return widget.content === this.content
+  }
+
+  updateDOM(element: HTMLElement): boolean {
+    element.textContent = this.content
+    return true
+  }
+
+  ignoreEvent(event: Event) {
+    return event.type !== 'mousedown' && event.type !== 'mouseup'
+  }
+}

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

@@ -167,6 +167,7 @@ export const LaTeXLanguage = LRLanguage.define({
         DoubleDollar: t.keyword,
         Tilde: t.keyword,
         Ampersand: t.keyword,
+        LineBreakCtrlSym: t.keyword,
         Comment: t.comment,
         'UsePackage/OptionalArgument/ShortOptionalArg/Normal': t.attributeValue,
         'UsePackage/ShortTextArgument/ShortArg/Normal': t.tagName,

+ 9 - 2
services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar

@@ -107,7 +107,8 @@
     OpenParenCtrlSym,
     CloseParenCtrlSym,
     OpenBracketCtrlSym,
-    CloseBracketCtrlSym
+    CloseBracketCtrlSym,
+    LineBreakCtrlSym
 }
 
 @tokens {
@@ -344,6 +345,11 @@ UnknownCommand {
 Command {
       KnownCommand
     | UnknownCommand
+    | KnownCtrlSym
+}
+
+KnownCtrlSym {
+  LineBreakCtrlSym
 }
 
 textBase {
@@ -464,6 +470,7 @@ DefinitionFragment {
     | CloseParenCtrlSym
     | OpenBracketCtrlSym
     | CloseBracketCtrlSym
+    | KnownCtrlSym
     | BlankLine
     | NewLine
     | Normal
@@ -660,7 +667,7 @@ MathTextCommand {
 
 MathCommand { CtrlSeq }
 
-MathCtrlSym { CtrlSym }
+MathCtrlSym { CtrlSym | KnownCtrlSym }
 
 MathGroup {
     OpenBrace Math? CloseBrace

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

@@ -49,6 +49,7 @@ import {
   CloseParenCtrlSym,
   OpenBracketCtrlSym,
   CloseBracketCtrlSym,
+  LineBreakCtrlSym,
   // Sectioning commands
   BookCtrlSeq,
   PartCtrlSeq,
@@ -748,6 +749,7 @@ const otherKnownCtrlSyms = {
   '\\)': CloseParenCtrlSym,
   '\\[': OpenBracketCtrlSym,
   '\\]': CloseBracketCtrlSym,
+  '\\\\': LineBreakCtrlSym,
 }
 
 export const specializeCtrlSym = (name, terms) => {

+ 6 - 0
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx

@@ -594,6 +594,12 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
       cy.get('@first-line').type('Test\\~test')
       cy.get('@first-line').should('have.text', 'Test~test')
     })
+
+    it('decorates line breaks', function () {
+      cy.get('@first-line').type('Test \\\\ test')
+      cy.get('@second-line').click()
+      cy.get('@first-line').should('have.text', 'Test ↩ test')
+    })
   })
 
   describe('decorates theorems', function () {