From a2cff207a7c79ef0cafa4965050cbc79b141824e Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Fri, 4 Aug 2023 08:37:36 +0100 Subject: [PATCH] [cm6] Add LineBreak to LaTeX grammar, highlight and decorate (#13742) GitOrigin-RevId: 64c54676e0284b7187678eedcc5096b1aa284cc0 --- .../extensions/visual/atomic-decorations.ts | 8 ++++++ .../visual/utils/typeset-content.ts | 13 +++------ .../extensions/visual/visual-theme.ts | 3 +++ .../visual/visual-widgets/indicator.ts | 27 +++++++++++++++++++ .../languages/latex/latex-language.ts | 1 + .../source-editor/lezer-latex/latex.grammar | 11 ++++++-- .../source-editor/lezer-latex/tokens.mjs | 2 ++ .../codemirror-editor-visual.spec.tsx | 6 +++++ 8 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/indicator.ts diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/atomic-decorations.ts b/services/web/frontend/js/features/source-editor/extensions/visual/atomic-decorations.ts index 995820e660..85a4099c00 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/atomic-decorations.ts +++ b/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 diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/utils/typeset-content.ts b/services/web/frontend/js/features/source-editor/extensions/visual/utils/typeset-content.ts index 4e0e5efab1..1b9a348e49 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/utils/typeset-content.ts +++ b/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 } diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/visual-theme.ts b/services/web/frontend/js/features/source-editor/extensions/visual/visual-theme.ts index e3d1349ead..4cc7ca3b22 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/visual-theme.ts +++ b/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', diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/indicator.ts b/services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/indicator.ts new file mode 100644 index 0000000000..88eea0e6af --- /dev/null +++ b/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' + } +} diff --git a/services/web/frontend/js/features/source-editor/languages/latex/latex-language.ts b/services/web/frontend/js/features/source-editor/languages/latex/latex-language.ts index ecdf976875..1a68acc1e2 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/latex-language.ts +++ b/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, diff --git a/services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar b/services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar index 5d16d41f39..ef36c32d02 100644 --- a/services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar +++ b/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 diff --git a/services/web/frontend/js/features/source-editor/lezer-latex/tokens.mjs b/services/web/frontend/js/features/source-editor/lezer-latex/tokens.mjs index 0cde16787f..3c7729a42a 100644 --- a/services/web/frontend/js/features/source-editor/lezer-latex/tokens.mjs +++ b/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) => { diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx index d0383171e8..735fd54b62 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx @@ -594,6 +594,12 @@ describe(' 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 () {