mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 06:03:45 +01:00
Add text formatting commands to the LaTeX grammar (#19607)
GitOrigin-RevId: f69cd323992c80de3f0a458a637fa8f160017076
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { ensureSyntaxTree, syntaxTree } from '@codemirror/language'
|
||||
import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state'
|
||||
import { SyntaxNode, Tree } from '@lezer/common'
|
||||
import { isUnknownCommandWithName } from './common'
|
||||
import { ListEnvironment } from '../../lezer-latex/latex.terms.mjs'
|
||||
|
||||
const HUNDRED_MS = 100
|
||||
@@ -125,14 +124,15 @@ export const lastAncestorAtEndPosition = (
|
||||
node: SyntaxNode | null | undefined,
|
||||
to: number
|
||||
): SyntaxNode | null => {
|
||||
for (let ancestor = node; ancestor; ancestor = ancestor.parent) {
|
||||
if (ancestor.parent?.to === to) {
|
||||
continue
|
||||
} else if (ancestor.to === to) {
|
||||
return ancestor
|
||||
}
|
||||
let lastAncestor: SyntaxNode | null = null
|
||||
for (
|
||||
let ancestor = node;
|
||||
ancestor && ancestor.to === to;
|
||||
ancestor = ancestor.parent
|
||||
) {
|
||||
lastAncestor = ancestor
|
||||
}
|
||||
return null
|
||||
return lastAncestor
|
||||
}
|
||||
|
||||
export const descendantsOfNodeWithType = (
|
||||
@@ -226,37 +226,6 @@ export const commonAncestor = (
|
||||
return null
|
||||
}
|
||||
|
||||
export const withinFormattingCommand = (state: EditorState) => {
|
||||
const tree = syntaxTree(state)
|
||||
|
||||
return (command: string): boolean => {
|
||||
const isFormattedText = (range: SelectionRange): boolean => {
|
||||
const nodeLeft = tree.resolveInner(range.from, -1)
|
||||
const formattingCommandLeft = matchingAncestor(nodeLeft, node =>
|
||||
isUnknownCommandWithName(node, command, state)
|
||||
)
|
||||
if (!formattingCommandLeft) {
|
||||
return false
|
||||
}
|
||||
|
||||
// We need to check the other end of the selection, and ensure that they
|
||||
// share a common formatting command ancestor
|
||||
const nodeRight = tree.resolveInner(range.to, 1)
|
||||
const ancestor = commonAncestor(formattingCommandLeft, nodeRight)
|
||||
if (!ancestor) {
|
||||
return false
|
||||
}
|
||||
|
||||
const formattingAncestor = matchingAncestor(ancestor, node =>
|
||||
isUnknownCommandWithName(node, command, state)
|
||||
)
|
||||
return Boolean(formattingAncestor)
|
||||
}
|
||||
|
||||
return state.selection.ranges.every(isFormattedText)
|
||||
}
|
||||
}
|
||||
|
||||
export type ListEnvironmentName = 'itemize' | 'enumerate' | 'description'
|
||||
|
||||
export const listDepthForNode = (node: SyntaxNode) => {
|
||||
|
||||
@@ -58,35 +58,6 @@ export const getOptionalArgumentText = (
|
||||
): string | undefined => {
|
||||
const shortArgNode = optionalArgumentNode.getChild('ShortOptionalArg')
|
||||
if (shortArgNode) {
|
||||
const shortArgNodeText = state.doc.sliceString(
|
||||
shortArgNode.from,
|
||||
shortArgNode.to
|
||||
)
|
||||
return shortArgNodeText
|
||||
return state.doc.sliceString(shortArgNode.from, shortArgNode.to)
|
||||
}
|
||||
}
|
||||
|
||||
export const resolveNodeAtPos = (
|
||||
state: EditorState,
|
||||
pos: number,
|
||||
side?: -1 | 0 | 1
|
||||
) => ensureSyntaxTree(state, pos, HUNDRED_MS)?.resolveInner(pos, side) ?? null
|
||||
|
||||
export const isUnknownCommandWithName = (
|
||||
node: SyntaxNode,
|
||||
command: string,
|
||||
state: EditorState
|
||||
): boolean => {
|
||||
if (!node.type.is('UnknownCommand')) {
|
||||
return false
|
||||
}
|
||||
const commandNameNode = node.getChild('CtrlSeq')
|
||||
if (!commandNameNode) {
|
||||
return false
|
||||
}
|
||||
const commandName = state.doc.sliceString(
|
||||
commandNameNode.from,
|
||||
commandNameNode.to
|
||||
)
|
||||
return commandName === command
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { EditorState, SelectionRange } from '@codemirror/state'
|
||||
import { syntaxTree } from '@codemirror/language'
|
||||
import {
|
||||
commonAncestor,
|
||||
matchingAncestor,
|
||||
} from '@/features/source-editor/utils/tree-operations/ancestors'
|
||||
|
||||
export type FormattingCommand = '\\textbf' | '\\textit'
|
||||
export type FormattingNodeType = string | number
|
||||
|
||||
export const formattingCommandMap: Record<
|
||||
FormattingCommand,
|
||||
FormattingNodeType
|
||||
> = {
|
||||
'\\textbf': 'TextBoldCommand',
|
||||
'\\textit': 'TextItalicCommand',
|
||||
}
|
||||
|
||||
export const withinFormattingCommand = (state: EditorState) => {
|
||||
const tree = syntaxTree(state)
|
||||
|
||||
return (command: FormattingCommand): boolean => {
|
||||
const nodeType = formattingCommandMap[command]
|
||||
|
||||
const isFormattedText = (range: SelectionRange): boolean => {
|
||||
const nodeLeft = tree.resolveInner(range.from, -1)
|
||||
const formattingCommandLeft = matchingAncestor(nodeLeft, node =>
|
||||
node.type.is(nodeType)
|
||||
)
|
||||
if (!formattingCommandLeft) {
|
||||
return false
|
||||
}
|
||||
|
||||
// We need to check the other end of the selection, and ensure that they
|
||||
// share a common formatting command ancestor
|
||||
const nodeRight = tree.resolveInner(range.to, 1)
|
||||
const ancestor = commonAncestor(formattingCommandLeft, nodeRight)
|
||||
if (!ancestor) {
|
||||
return false
|
||||
}
|
||||
|
||||
const formattingAncestor = matchingAncestor(ancestor, node =>
|
||||
node.type.is(nodeType)
|
||||
)
|
||||
return Boolean(formattingAncestor)
|
||||
}
|
||||
|
||||
return state.selection.ranges.every(isFormattedText)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ export {
|
||||
iterateDescendantsOf,
|
||||
previousSiblingIs,
|
||||
nextSiblingIs,
|
||||
isUnknownCommandWithName,
|
||||
} from './tree-operations/common'
|
||||
|
||||
export {
|
||||
|
||||
Reference in New Issue
Block a user