mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
Only show "Jump to command definition" for right-click on command name (#36191)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> GitOrigin-RevId: 05811783a327fb641f6c677eaa1efeb8a87668fc
This commit is contained in:
committed by
Copybot
co-authored by
Claude Fable 5
parent
1681343782
commit
10f10ca95f
@@ -1,11 +1,9 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { SyntaxNode } from '@lezer/common'
|
||||
import { syntaxTree } from '@codemirror/language'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { CommandDefinition } from '../utils/command-definitions/command-definition'
|
||||
import { getCommandDefinitionIndexer } from '../utils/command-definitions/command-definition-indexer'
|
||||
import { commandNameAtPos } from '../utils/command-definitions/command-name-at-pos'
|
||||
import { useCodeMirrorStateContext } from '../components/codemirror-context'
|
||||
import { contextMenuStateField } from '../extensions/context-menu'
|
||||
|
||||
@@ -71,24 +69,3 @@ export function useContextMenuCommandDefinition() {
|
||||
const commandDefinition = useCommandDefinition(commandName, tooltip)
|
||||
return { commandName, commandDefinition }
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the control sequence (e.g. `\mycmd`) at a document position, or null
|
||||
* if the position is not on a command. Walks up from the innermost node to the
|
||||
* nearest node carrying a control-sequence token, mirroring the command-usage
|
||||
* logic in tree-operations/commands.ts.
|
||||
*/
|
||||
function commandNameAtPos(state: EditorState, pos: number): string | null {
|
||||
const tree = syntaxTree(state)
|
||||
// Bias right (as resolveCommandNode does) so a click on the command's first
|
||||
// character resolves into it rather than the preceding token.
|
||||
let node: SyntaxNode | null = tree.resolveInner(pos, 1)
|
||||
for (; node; node = node.parent) {
|
||||
const ctrlSeq = node.getChild('$CtrlSeq')
|
||||
if (ctrlSeq && !ctrlSeq.type.is('$CtrlSym')) {
|
||||
const name = state.sliceDoc(ctrlSeq.from, ctrlSeq.to)
|
||||
return name.length > 0 ? name : null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { SyntaxNode } from '@lezer/common'
|
||||
import { syntaxTree } from '@codemirror/language'
|
||||
|
||||
/**
|
||||
* Resolve the control sequence (e.g. `\mycmd`) at a document position, or null
|
||||
* if the position is not on a command. Walks up from the innermost node to the
|
||||
* nearest control-sequence node, mirroring the command-usage
|
||||
* logic in tree-operations/commands.ts. Control symbols (e.g. `\\`, `\%`) are
|
||||
* excluded: they are also in the `$CtrlSeq` group, but are never user-defined
|
||||
* commands.
|
||||
*/
|
||||
export function commandNameAtPos(
|
||||
state: EditorState,
|
||||
pos: number
|
||||
): string | null {
|
||||
const tree = syntaxTree(state)
|
||||
// Bias right (as resolveCommandNode does) so a click on the command's first
|
||||
// character resolves into it rather than the preceding token.
|
||||
let node: SyntaxNode | null = tree.resolveInner(pos, 1)
|
||||
for (; node; node = node.parent) {
|
||||
if (node.type.is('$CtrlSeq') && !node.type.is('$CtrlSym')) {
|
||||
const name = state.sliceDoc(node.from, node.to)
|
||||
return name.length > 0 ? name : null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { LanguageSupport } from '@codemirror/language'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { expect } from 'chai'
|
||||
import { commandNameAtPos } from '@/features/source-editor/utils/command-definitions/command-name-at-pos'
|
||||
import { LaTeXLanguage } from '@/features/source-editor/languages/latex/latex-language'
|
||||
|
||||
const latex = new LanguageSupport(LaTeXLanguage)
|
||||
|
||||
const makeState = (doc: string): EditorState =>
|
||||
EditorState.create({ doc, extensions: [latex] })
|
||||
|
||||
describe('commandNameAtPos', function () {
|
||||
it('resolves the command name when the position is on the command', function () {
|
||||
const doc = 'before \\mycmd{arg} after'
|
||||
const state = makeState(doc)
|
||||
const from = doc.indexOf('\\mycmd')
|
||||
expect(commandNameAtPos(state, from)).to.equal('\\mycmd')
|
||||
expect(commandNameAtPos(state, from + 3)).to.equal('\\mycmd')
|
||||
})
|
||||
|
||||
it('resolves a known command name', function () {
|
||||
const doc = 'some \\textbf{bold} text'
|
||||
const state = makeState(doc)
|
||||
expect(commandNameAtPos(state, doc.indexOf('\\textbf'))).to.equal(
|
||||
'\\textbf'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns null for a control symbol', function () {
|
||||
const doc = 'linebreak \\\\ and \\% percent'
|
||||
const state = makeState(doc)
|
||||
expect(commandNameAtPos(state, doc.indexOf('\\\\'))).to.equal(null)
|
||||
expect(commandNameAtPos(state, doc.indexOf('\\%'))).to.equal(null)
|
||||
})
|
||||
|
||||
it('returns null on plain text', function () {
|
||||
const doc = 'plain text \\mycmd{arg}'
|
||||
const state = makeState(doc)
|
||||
expect(commandNameAtPos(state, 0)).to.equal(null)
|
||||
})
|
||||
|
||||
it("returns null inside a command's argument", function () {
|
||||
const doc = '\\mycmd{argument}'
|
||||
const state = makeState(doc)
|
||||
expect(commandNameAtPos(state, doc.indexOf('argument') + 3)).to.equal(null)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user