diff --git a/services/web/frontend/js/features/source-editor/languages/latex/close-bracket-config.ts b/services/web/frontend/js/features/source-editor/languages/latex/close-bracket-config.ts index f993448c68..1c13487ea8 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/close-bracket-config.ts +++ b/services/web/frontend/js/features/source-editor/languages/latex/close-bracket-config.ts @@ -1,9 +1,5 @@ import { EditorState, SelectionRange, Text } from '@codemirror/state' -import { - CloseBracketConfig, - completionStatus, - prevChar, -} from '@codemirror/autocomplete' +import { CloseBracketConfig, prevChar } from '@codemirror/autocomplete' export const closeBracketConfig: CloseBracketConfig = { brackets: ['$', '$$', '[', '{', '('], @@ -74,10 +70,6 @@ export const closeBracketConfig: CloseBracketConfig = { // don't auto-close \{ return open } - // avoid auto-closing curly brackets when autocomplete is open - if (completionStatus(state)) { - return open - } return open + close } diff --git a/services/web/frontend/js/features/source-editor/languages/latex/complete.ts b/services/web/frontend/js/features/source-editor/languages/latex/complete.ts index 8f81695427..9e44550b74 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/complete.ts +++ b/services/web/frontend/js/features/source-editor/languages/latex/complete.ts @@ -6,7 +6,10 @@ import { } from '@codemirror/autocomplete' import { customEndCompletions } from './completions/environments' import { customCommandCompletions } from './completions/doc-commands' -import { customEnvironmentCompletions } from './completions/doc-environments' +import { + customEnvironmentCompletions, + findEnvironmentsInDoc, +} from './completions/doc-environments' import { Completions } from './completions/types' import { buildReferenceCompletions } from './completions/references' import { buildPackageCompletions } from './completions/packages' @@ -406,12 +409,6 @@ export const beginEnvironmentCompletionSource: CompletionSource = context => { return null } - // this completion source must only be active when the \begin command has a closing brace - const closeBrace = envNameGroup.getChild('CloseBrace') - if (!closeBrace) { - return null - } - const envName = envNameGroup.getChild('$EnvName') if (!envName) { return null @@ -419,10 +416,19 @@ export const beginEnvironmentCompletionSource: CompletionSource = context => { const name = context.state.sliceDoc(envName.from, envName.to) + // if not directly after `\begin{…}`, exclude known environments + if (context.pos !== envNameGroup.to) { + const existingEnvironmentNames = findEnvironmentsInDoc(context) + if (existingEnvironmentNames.has(name)) { + return null + } + } + const completion = { label: `\\begin{${name}} …`, apply: applySnippet(snippet(name)), extend: extendOverUnpairedClosingBrace, + boost: -99, } return { diff --git a/services/web/frontend/js/features/source-editor/languages/latex/completions/doc-environments.ts b/services/web/frontend/js/features/source-editor/languages/latex/completions/doc-environments.ts index 69631c78b0..654a66938d 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/completions/doc-environments.ts +++ b/services/web/frontend/js/features/source-editor/languages/latex/completions/doc-environments.ts @@ -22,7 +22,7 @@ export function customEnvironmentCompletions(context: CompletionContext) { return completions } -const findEnvironmentsInDoc = (context: CompletionContext) => { +export const findEnvironmentsInDoc = (context: CompletionContext) => { const result = new Set() const environmentNamesProjection: ProjectionResult = diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx index b6a91fad2f..87b16b64ff 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx @@ -321,10 +321,10 @@ describe('autocomplete', { scrollBehavior: false }, function () { cy.get('.cm-line').eq(28).click().as('line') cy.get('@line').type('\\begin{{}ab') cy.findAllByRole('option').as('options') - cy.get('@options').should('have.length', 4) + cy.get('@options').should('have.length', 5) - // ---- The environment being typed should not appear in the list - cy.get('@options').contains('\\begin{ab}').should('not.exist') + // ---- The environment being typed should appear in the list + cy.get('@options').contains('\\begin{ab}').should('exist') // ---- A new environment used elsewhere in the doc should appear next cy.get('@options') 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 d137ab73e3..47dab533aa 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 @@ -126,8 +126,8 @@ describe(' in Rich Text mode', function () { function (command) { cy.get('@first-line') .type(`\\${command}{`) - .should('have.text', `\\${command}{`) - .type('} ') // Should still show braces for empty commands + .should('have.text', `{}`) + .type('{rightArrow} ') .should('have.text', '{} ') .type('{Backspace}{leftArrow}test text') .should('have.text', '{test text}') @@ -148,10 +148,8 @@ describe(' in Rich Text mode', function () { ]).it('handles \\%s sectioning command', function (command) { cy.get('@first-line') .type(`\\${command}{`) - .should('have.text', `\\${command}{`) - .type(`}`) .should('have.text', `\\${command}{}`) - .type(' ') + .type('{rightArrow} ') .should('have.text', `\\${command}{} `) // Press enter before closing brace .type('{Backspace}{leftArrow}title{leftArrow}{Enter}') @@ -160,24 +158,14 @@ describe(' in Rich Text mode', function () { .should('exist') }) - forEach([ - 'textsc', - 'texttt', - 'sout', - 'emph', - ['verb', '|', '|'], - 'url', - 'caption', - ]).it( + forEach(['textsc', 'texttt', 'sout', 'emph', 'url', 'caption']).it( 'handles \\%s text', - function (command, openingBrace = '{', closingBrace = '}') { + function (command) { cy.get('@first-line') - .type(`\\${command}${openingBrace}`) - .should('have.text', `\\${command}${openingBrace}`) - .type(`${closingBrace}`) - .should('have.text', `\\${command}${openingBrace}${closingBrace}`) - .type(' ') - .should('have.text', `\\${command}${openingBrace}${closingBrace} `) + .type(`\\${command}{`) + .should('have.text', `\\${command}{}`) + .type('{rightArrow} ') + .should('have.text', `\\${command}{} `) .type('{Backspace}{leftArrow}test text{rightArrow} ') .should('have.text', 'test text ') .find(`.ol-cm-command-${command}`) @@ -185,6 +173,18 @@ describe(' in Rich Text mode', function () { } ) + it('handles \\verb text', function () { + cy.get('@first-line') + .type(`\\verb|`) + .should('have.text', `\\verb|`) + .type('| ') + .should('have.text', `\\verb|| `) + .type('{Backspace}{leftArrow}test text{rightArrow} ') + .should('have.text', 'test text ') + .find(`.ol-cm-command-verb`) + .should('exist') + }) + forEach([ ['ref', '🏷'], ['label', '🏷'],