From 3bd174631a3d2c337b65a68db8f455af1a92cb76 Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Thu, 20 Apr 2023 10:59:43 +0100 Subject: [PATCH] Merge pull request #12696 from overleaf/mj-rt-skip-more-preamble [cm6+rt] Update skip-preamble-cursor to skip \maketitle and abstract GitOrigin-RevId: 3c54d8159bfdb431763872790a2b82ac4cffc09f --- .../extensions/visual/atomic-decorations.ts | 34 +++++----- .../extensions/visual/skip-preamble-cursor.ts | 13 +++- .../source-editor/lezer-latex/latex.grammar | 6 +- .../source-editor/lezer-latex/tokens.mjs | 2 + .../utils/tree-operations/environments.ts | 64 ++++++++++++++++--- 5 files changed, 90 insertions(+), 29 deletions(-) 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 13d3f726d8..dcb37253ae 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 @@ -731,6 +731,23 @@ export const atomicDecorations = (options: Options) => { return false } } + } else if (nodeRef.type.is('Maketitle')) { + if (shouldDecorate(state, nodeRef)) { + const line = state.doc.lineAt(nodeRef.from) + const from = extendBackwardsOverEmptyLines(state.doc, line) + const to = extendForwardsOverEmptyLines(state.doc, line) + + if (shouldDecorate(state, { from, to })) { + decorations.push( + Decoration.replace({ + widget: new MakeTitleWidget(preamble), + block: true, + }).range(from, to) + ) + } + + return false + } } else if (nodeRef.type.is('Item')) { // only decorate \item inside a list if (currentListEnvironment) { @@ -823,23 +840,6 @@ export const atomicDecorations = (options: Options) => { widget: new TeXWidget(), }).range(nodeRef.from, nodeRef.to) ) - return false - } - } else if (commandName === '\\maketitle') { - if (shouldDecorate(state, nodeRef)) { - const line = state.doc.lineAt(nodeRef.from) - const from = extendBackwardsOverEmptyLines(state.doc, line) - const to = extendForwardsOverEmptyLines(state.doc, line) - - if (shouldDecorate(state, { from, to })) { - decorations.push( - Decoration.replace({ - widget: new MakeTitleWidget(preamble), - block: true, - }).range(from, to) - ) - } - return false } } else if (hasCharacterSubstitution(commandName)) { diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/skip-preamble-cursor.ts b/services/web/frontend/js/features/source-editor/extensions/visual/skip-preamble-cursor.ts index fbd71cda8e..9fd64d4a3e 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/skip-preamble-cursor.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/skip-preamble-cursor.ts @@ -1,7 +1,8 @@ import { EditorView, ViewPlugin } from '@codemirror/view' import { EditorSelection } from '@codemirror/state' -import { findDocumentEnvironment } from '../../utils/tree-operations/environments' +import { findStartOfDocumentContent } from '../../utils/tree-operations/environments' import { syntaxTree } from '@codemirror/language' +import { extendForwardsOverEmptyLines } from './selection' export const skipPreambleWithCursor = ViewPlugin.define((view: EditorView) => { let checkedOnce = false return { @@ -21,10 +22,16 @@ export const skipPreambleWithCursor = ViewPlugin.define((view: EditorView) => { ) ) { setTimeout(() => { - const position = findDocumentEnvironment(view.state) + const position = + extendForwardsOverEmptyLines( + update.state.doc, + update.state.doc.lineAt( + findStartOfDocumentContent(update.state) ?? 0 + ) + ) + 1 view.dispatch({ selection: EditorSelection.cursor( - Math.min(position ? position + 1 : 0, update.state.doc.length) + Math.min(position, update.state.doc.length) ), }) }, 0) 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 8362e7f43e..38631ce021 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 @@ -84,7 +84,8 @@ CenteringCtrlSeq, BibliographyCtrlSeq, BibliographyStyleCtrlSeq, - AuthorCtrlSeq + AuthorCtrlSeq, + MaketitleCtrlSeq } @external specialize {EnvName} specializeEnvName from "./tokens.mjs" { @@ -310,6 +311,9 @@ KnownCommand { } | Item { ItemCtrlSeq optionalWhitespace? + } | + Maketitle { + MaketitleCtrlSeq optionalWhitespace? } } 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 f5cd7d16d2..3236cab98b 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 @@ -65,6 +65,7 @@ import { BibliographyStyleCtrlSeq, CenteringCtrlSeq, ListEnvName, + MaketitleCtrlSeq, } from './latex.terms.mjs' function nameChar(ch) { @@ -635,6 +636,7 @@ const otherKnowncommands = { '\\centering': CenteringCtrlSeq, '\\bibliography': BibliographyCtrlSeq, '\\bibliographystyle': BibliographyStyleCtrlSeq, + '\\maketitle': MaketitleCtrlSeq, } // specializer for control sequences // return new tokens for specific control sequences diff --git a/services/web/frontend/js/features/source-editor/utils/tree-operations/environments.ts b/services/web/frontend/js/features/source-editor/utils/tree-operations/environments.ts index db0c4e924c..62b369e5b4 100644 --- a/services/web/frontend/js/features/source-editor/utils/tree-operations/environments.ts +++ b/services/web/frontend/js/features/source-editor/utils/tree-operations/environments.ts @@ -1,6 +1,6 @@ import { ensureSyntaxTree } from '@codemirror/language' import { EditorState } from '@codemirror/state' -import { SyntaxNode, SyntaxNodeRef } from '@lezer/common' +import { SyntaxNode, SyntaxNodeRef, Tree } from '@lezer/common' import { previousSiblingIs } from './common' import { NodeIntersectsChangeFn, ProjectionItem } from './projection' @@ -138,21 +138,69 @@ export const cursorIsAtEndEnvironment = ( } } -export const findDocumentEnvironment = (state: EditorState): number | null => { - const tree = ensureSyntaxTree(state, state.doc.length, HUNDRED_MS) - let position: number | null = null +const findStartOfDocumentEnvironment = (tree: Tree): number | null => { + const docEnvironment = findNodeInDocument(tree, 'DocumentEnvironment') + return docEnvironment?.getChild('Content')?.from || null +} + +const findStartOfAbstractEnvironment = ( + tree: Tree, + state: EditorState +): number | null => { + const abstractEnvironment = findNodeInDocument( + tree, + (nodeRef: SyntaxNodeRef) => { + return Boolean( + nodeRef.type.is('$Environment') && + getEnvironmentName(nodeRef.node, state) === 'abstract' + ) + } + ) + return abstractEnvironment?.getChild('Content')?.from || null +} + +const findMaketitleCommand = (tree: Tree): number | null => { + const maketitle = findNodeInDocument(tree, 'Maketitle') + return maketitle?.to ?? null +} + +const findNodeInDocument = ( + tree: Tree, + predicate: number | string | ((node: SyntaxNodeRef) => boolean) +): SyntaxNode | null => { + let node: SyntaxNode | null = null + const predicateFn = + typeof predicate !== 'function' + ? (nodeRef: SyntaxNodeRef) => { + return nodeRef.type.is(predicate) + } + : predicate tree?.iterate({ enter(nodeRef) { - if (position !== null) { + if (node !== null) { return false } - if (nodeRef.type.is('DocumentEnvironment')) { - position = nodeRef.node.getChild('Content')?.from || null + if (predicateFn(nodeRef)) { + node = nodeRef.node return false } }, }) - return position + return node +} + +export const findStartOfDocumentContent = ( + state: EditorState +): number | null => { + const tree = ensureSyntaxTree(state, state.doc.length, HUNDRED_MS) + if (!tree) { + return null + } + return ( + findStartOfAbstractEnvironment(tree, state) ?? + findMaketitleCommand(tree) ?? + findStartOfDocumentEnvironment(tree) + ) } /**