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

[visual] Handle selections adjacent to lists and section headings (#13581)

GitOrigin-RevId: 35b289102110f88587679740eeed575e16f6788b
Alf Eaton 3 лет назад
Родитель
Сommit
52d9ee79a3

+ 4 - 5
services/web/frontend/js/features/source-editor/extensions/toolbar/lists.ts

@@ -17,6 +17,7 @@ import {
   ancestorOfNodeWithType,
   ancestorOfNodeWithType,
   ancestorWithType,
   ancestorWithType,
   descendantsOfNodeWithType,
   descendantsOfNodeWithType,
+  wrappedNodeOfType,
 } from '../../utils/tree-operations/ancestors'
 } from '../../utils/tree-operations/ancestors'
 import { getEnvironmentName } from '../../utils/tree-operations/environments'
 import { getEnvironmentName } from '../../utils/tree-operations/environments'
 import { ListEnvironment } from '../../lezer-latex/latex.terms.mjs'
 import { ListEnvironment } from '../../lezer-latex/latex.terms.mjs'
@@ -195,11 +196,9 @@ const toggleListForRange = (
   range: SelectionRange,
   range: SelectionRange,
   environment: string
   environment: string
 ) => {
 ) => {
-  const ancestorNode = ancestorNodeOfType(
-    view.state,
-    range.head,
-    ListEnvironment
-  )
+  const ancestorNode =
+    ancestorNodeOfType(view.state, range.head, ListEnvironment) ??
+    wrappedNodeOfType(view.state, range, ListEnvironment)
 
 
   if (ancestorNode) {
   if (ancestorNode) {
     const beginEnvNode = ancestorNode.getChild('BeginEnv')
     const beginEnvNode = ancestorNode.getChild('BeginEnv')

+ 33 - 8
services/web/frontend/js/features/source-editor/extensions/toolbar/sections.ts

@@ -1,7 +1,7 @@
 import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state'
 import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state'
 import { EditorView } from '@codemirror/view'
 import { EditorView } from '@codemirror/view'
 import { syntaxTree } from '@codemirror/language'
 import { syntaxTree } from '@codemirror/language'
-import { ancestorsOfNodeWithType } from '../../utils/tree-operations/ancestors'
+import { ancestorOfNodeWithType } from '../../utils/tree-operations/ancestors'
 import { SyntaxNode } from '@lezer/common'
 import { SyntaxNode } from '@lezer/common'
 
 
 export const findCurrentSectionHeadingLevel = (state: EditorState) => {
 export const findCurrentSectionHeadingLevel = (state: EditorState) => {
@@ -25,13 +25,38 @@ export const rangeInfo = (
   range: SelectionRange
   range: SelectionRange
 ): RangeInfo => {
 ): RangeInfo => {
   const tree = syntaxTree(state)
   const tree = syntaxTree(state)
-  const node = tree.resolveInner(range.anchor)
-  const command = ancestorsOfNodeWithType(node, 'SectioningCommand').next()
-    .value
-  const ctrlSeq = command?.firstChild
-  const level = ctrlSeq
-    ? state.sliceDoc(ctrlSeq.from + 1, ctrlSeq.to).trim()
-    : 'text'
+
+  const fromNode = tree.resolveInner(range.from, 1)
+  const fromAncestor = ancestorOfNodeWithType(fromNode, 'SectioningCommand')
+
+  const toNode = tree.resolveInner(range.to, -1)
+  const toAncestor = ancestorOfNodeWithType(toNode, 'SectioningCommand')
+
+  const command = fromAncestor ?? toAncestor
+
+  // from and to are both outside section heading
+  if (!command) {
+    return { range, level: 'text' }
+  }
+
+  if (fromAncestor && toAncestor) {
+    // from and to are inside different section headings
+    if (fromAncestor !== toAncestor) {
+      return { range, level: 'text' }
+    }
+  } else {
+    // the range isn't empty and only one end is inside a section heading
+    if (!range.empty) {
+      return { range, level: 'text' }
+    }
+  }
+
+  const ctrlSeq = command.firstChild
+  if (!ctrlSeq) {
+    return { range, level: 'text' }
+  }
+
+  const level = state.sliceDoc(ctrlSeq.from + 1, ctrlSeq.to).trim()
 
 
   return { command, ctrlSeq, level, range }
   return { command, ctrlSeq, level, range }
 }
 }

+ 22 - 0
services/web/frontend/js/features/source-editor/utils/tree-operations/ancestors.ts

@@ -64,6 +64,28 @@ export function getAncestorStack(
   return stack.reverse()
   return stack.reverse()
 }
 }
 
 
+export const wrappedNodeOfType = (
+  state: EditorState,
+  range: SelectionRange,
+  type: string | number
+): SyntaxNode | null => {
+  if (range.empty) {
+    return null
+  }
+
+  const ancestorNode = ancestorNodeOfType(state, range.from, type, 1)
+
+  if (
+    ancestorNode &&
+    ancestorNode.from === range.from &&
+    ancestorNode.to === range.to
+  ) {
+    return ancestorNode
+  }
+
+  return null
+}
+
 export const ancestorNodeOfType = (
 export const ancestorNodeOfType = (
   state: EditorState,
   state: EditorState,
   pos: number,
   pos: number,