Merge pull request #33391 from overleaf/em-bibtex-projection-32449

Use a projected state field for BibTeX entries in the editor

GitOrigin-RevId: 5034be8bdc0cb4b9d854135ac117046c1b3750e7
This commit is contained in:
Eric Mc Sween
2026-05-11 12:33:58 -04:00
committed by Copybot
parent 0f3ae5ac5b
commit aca60c02c0
6 changed files with 105 additions and 3 deletions

View File

@@ -79,6 +79,7 @@ export const enterNode = (
items.push({
line: state.doc.lineAt(node.from).number,
toLine: state.doc.lineAt(node.to).number,
title: commandName,
from: node.from,
to: node.to,
@@ -106,6 +107,7 @@ export const enterNode = (
items.push({
line: state.doc.lineAt(node.from).number,
toLine: state.doc.lineAt(node.to).number,
title: commandName,
from: node.from,
to: node.to,
@@ -127,6 +129,7 @@ export const enterNode = (
}
items.push({
line: state.doc.lineAt(node.from).number,
toLine: state.doc.lineAt(node.to).number,
title: commandName,
from: node.from,
to: node.to,
@@ -180,6 +183,7 @@ export const enterNode = (
items.push({
line: state.doc.lineAt(commandNode.from).number,
toLine: state.doc.lineAt(commandNode.to).number,
title: text,
from: commandNode.from,
to: commandNode.to,

View File

@@ -45,6 +45,7 @@ export const enterNode = (
from: envNameNode.from,
to: envNameNode.to,
line: state.doc.lineAt(envNameNode.from).number,
toLine: state.doc.lineAt(envNameNode.to).number,
type: 'usage',
raw: state.sliceDoc(node.from, node.to),
}
@@ -74,6 +75,7 @@ export const enterNode = (
from: envNameNode.from,
to: envNameNode.to,
line: state.doc.lineAt(envNameNode.from).number,
toLine: state.doc.lineAt(envNameNode.to).number,
type: 'definition',
raw: state.sliceDoc(node.from, node.to),
}

View File

@@ -155,6 +155,7 @@ export const enterNode = (
const thisNode = {
line: state.doc.lineAt(command.from).number,
toLine: state.doc.lineAt(command.to).number,
title: getEntryText(state, name),
from: command.from,
to: command.to,
@@ -181,6 +182,7 @@ export const enterNode = (
: ''
const thisNode = {
line: state.doc.lineAt(beginEnv.from).number,
toLine: state.doc.lineAt(beginEnv.to).number,
title,
from: beginEnv.from,
to: beginEnv.to,

View File

@@ -12,6 +12,7 @@ export abstract class ProjectionItem {
readonly from: number = 0
readonly to: number = 0
readonly line: number = 0
readonly toLine: number = 0
}
/* eslint-disable no-unused-vars */
@@ -48,9 +49,15 @@ export function updatePosition<T extends ProjectionItem>(
const { from, to } = item
const newFrom = transaction.changes.mapPos(from)
const newTo = transaction.changes.mapPos(to)
const lineNumber = transaction.state.doc.lineAt(newFrom).number
const newLine = transaction.state.doc.lineAt(newFrom).number
const newToLine = transaction.state.doc.lineAt(newTo).number
if (newFrom === from && newTo === to && lineNumber === item.line) {
if (
newFrom === from &&
newTo === to &&
newLine === item.line &&
newToLine === item.toLine
) {
// Optimisation - if the item hasn't moved, don't create a new object
// If items are not immutable this can introduce problems
return item
@@ -60,7 +67,8 @@ export function updatePosition<T extends ProjectionItem>(
...item,
from: newFrom,
to: newTo,
line: lineNumber,
line: newLine,
toLine: newToLine,
}
}

View File

@@ -94,6 +94,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
level: SECTION_LEVEL,
title: 'sec title',
line: 2,
toLine: 2,
},
{
from: 35,
@@ -101,6 +102,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
level: SUB_SECTION_LEVEL,
title: 'subsec title',
line: 4,
toLine: 4,
},
])
})
@@ -127,6 +129,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
level: SECTION_LEVEL,
title: 'sec title 1',
line: 2,
toLine: 2,
},
{
from: 37,
@@ -134,6 +137,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
level: SECTION_LEVEL,
title: 'sec title 2',
line: 4,
toLine: 4,
},
])
})
@@ -152,6 +156,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 16,
title: 'title ',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
])
@@ -170,6 +175,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 17,
title: 'title 1',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
])
@@ -189,6 +195,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 15,
title: 'title',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
])
@@ -208,6 +215,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 15,
title: 'title',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
{
@@ -215,6 +223,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 37,
title: 'subtitle',
line: 2,
toLine: 2,
level: SUB_SECTION_LEVEL,
},
])
@@ -229,6 +238,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 15,
title: 'title',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
{
@@ -236,6 +246,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 38,
title: 'subtitle',
line: 3,
toLine: 3,
level: SUB_SECTION_LEVEL,
},
])
@@ -254,6 +265,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 15,
title: 'title',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
])
@@ -282,6 +294,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 17,
title: 'section',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
{
@@ -289,6 +302,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 72,
title: 'subsubsection',
line: 3,
toLine: 3,
level: SUB_SUB_SECTION_LEVEL,
},
])
@@ -304,6 +318,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 17,
title: 'section',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
{
@@ -311,6 +326,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 41,
title: 'subsection',
line: 2,
toLine: 2,
level: SUB_SECTION_LEVEL,
},
{
@@ -318,6 +334,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 71,
title: 'subsubsection',
line: 3,
toLine: 3,
level: SUB_SUB_SECTION_LEVEL,
},
])
@@ -343,6 +360,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 17,
title: 'section',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
])
@@ -373,6 +391,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 11,
title: 'book',
line: 1,
toLine: 1,
level: BOOK_LEVEL,
},
{
@@ -380,6 +399,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 23,
title: 'part',
line: 2,
toLine: 2,
level: PART_LEVEL,
},
{
@@ -387,6 +407,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 41,
title: 'chapter',
line: 3,
toLine: 3,
level: CHAPTER_LEVEL,
},
{
@@ -394,6 +415,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 59,
title: 'section',
line: 4,
toLine: 4,
level: SECTION_LEVEL,
},
{
@@ -401,6 +423,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 83,
title: 'subsection',
line: 5,
toLine: 5,
level: SUB_SECTION_LEVEL,
},
{
@@ -408,6 +431,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 113,
title: 'subsubsection',
line: 6,
toLine: 6,
level: SUB_SUB_SECTION_LEVEL,
},
{
@@ -415,6 +439,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 135,
title: 'paragraph',
line: 7,
toLine: 7,
level: PARAGRAPH_LEVEL,
},
{
@@ -422,6 +447,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 163,
title: 'subparagraph',
line: 8,
toLine: 8,
level: SUB_PARAGRAPH_LEVEL,
},
])
@@ -443,6 +469,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 30,
title: 'section',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
])
@@ -466,6 +493,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 113,
title: 'The function f(x) = x^2: Properties of x.',
line: 1,
toLine: 1,
level: SECTION_LEVEL,
},
])
@@ -487,6 +515,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 22,
title: 'test',
line: 2,
toLine: 2,
level: SECTION_LEVEL,
},
{
@@ -494,6 +523,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 41,
title: 'test2',
line: 3,
toLine: 3,
level: SUB_SECTION_LEVEL,
},
])
@@ -516,6 +546,7 @@ describe('CodeMirror LaTeX-FileOutline', function () {
to: 28,
title: 'frame title',
line: 1,
toLine: 1,
level: FRAME_LEVEL,
},
])

View File

@@ -0,0 +1,55 @@
import { expect } from 'chai'
import { EditorState } from '@codemirror/state'
import {
ProjectionItem,
updatePosition,
} from '../../../../../frontend/js/features/source-editor/utils/tree-operations/projection'
class TestItem extends ProjectionItem {}
const itemAt = (
from: number,
to: number,
line: number,
toLine: number
): TestItem => Object.assign(new TestItem(), { from, to, line, toLine })
describe('updatePosition', function () {
it('returns the same instance when positions and lines are unchanged', function () {
const state = EditorState.create({ doc: 'first line\nsecond line' })
const tr = state.update({ changes: { from: 0, to: 0, insert: '' } })
const item = itemAt(11, 22, 2, 2)
expect(updatePosition(item, tr)).to.equal(item)
})
it('refreshes both line and toLine after an upstream insert adds lines', function () {
const state = EditorState.create({ doc: 'first line\nsecond line' })
const tr = state.update({
changes: { from: 0, to: 0, insert: 'top\nmiddle\n' },
})
// Item originally covered "second line" (lines 2..2) at offsets 11..22.
const item = itemAt(11, 22, 2, 2)
const updated = updatePosition(item, tr)
expect(updated).to.not.equal(item)
expect(updated.from).to.equal(22)
expect(updated.to).to.equal(33)
expect(updated.line).to.equal(4)
expect(updated.toLine).to.equal(4)
})
it('refreshes line and toLine independently for multi-line items', function () {
const state = EditorState.create({
doc: 'a\nbb\nccc\ndddd\neeeee',
})
// Item covers "bb\nccc" at offsets 2..8, on lines 2..3.
const item = itemAt(2, 8, 2, 3)
const tr = state.update({
changes: { from: 0, to: 0, insert: 'X\n' },
})
const updated = updatePosition(item, tr)
expect(updated.from).to.equal(4)
expect(updated.to).to.equal(10)
expect(updated.line).to.equal(3)
expect(updated.toLine).to.equal(4)
})
})