mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
Merge pull request #33146 from overleaf/copilot/fix-code-folding-bug
Fix code folding when a comment or blank line precedes an indented sectioning command GitOrigin-RevId: 2a955311c1ce073b2eb80fdfbf45d00705e22d69
This commit is contained in:
@@ -111,6 +111,20 @@ export const LaTeXLanguage = LRLanguage.define({
|
||||
if (sibling?.type.is(termsModule.NewLine)) {
|
||||
return { from: content!.from, to: sibling.from }
|
||||
}
|
||||
if (sibling?.type.is(termsModule.Comment)) {
|
||||
// A trailing comment line (e.g. `%`) before an indented
|
||||
// sectioning command should be included in the fold, but the
|
||||
// sectioning command itself should still appear on its own
|
||||
// line. The Comment node consumes its trailing newline, so
|
||||
// end the fold one position before the Comment ends.
|
||||
return { from: content!.from, to: sibling.to - 1 }
|
||||
}
|
||||
if (sibling?.type.is(termsModule.BlankLine)) {
|
||||
// Blank line(s) between previous content and an indented
|
||||
// sectioning command: include all but the last newline in the
|
||||
// fold (mirroring the BlankLine handling below).
|
||||
return { from: content!.from, to: sibling.to - 1 }
|
||||
}
|
||||
}
|
||||
if (lastChild.type.is(termsModule.BlankLine)) {
|
||||
// HACK: BlankLine can contain any number above 2 of \n's.
|
||||
|
||||
@@ -121,6 +121,136 @@ describe('CodeMirror LaTeX-folding', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a comment line before an indented sectioning command', function () {
|
||||
let view: EditorView, content: string[]
|
||||
|
||||
beforeEach(function () {
|
||||
// Reproduces a bug where a comment line followed by an indented
|
||||
// `\subsection` should not pull the next sectioning command's line
|
||||
// into the previous fold.
|
||||
content = [
|
||||
'\\section{1}',
|
||||
' Content',
|
||||
' \\subsection{1a}',
|
||||
' Content',
|
||||
' %',
|
||||
' \\subsection{1b}',
|
||||
' Content',
|
||||
]
|
||||
view = makeView(content)
|
||||
})
|
||||
|
||||
it('should fold subsection 1a up to the comment line, leaving subsection 1b on its own line', function () {
|
||||
const folds = _getFolds(view)
|
||||
const subsection1aFold = folds.find(
|
||||
fold => view.state.doc.lineAt(fold.from).number === 3
|
||||
)
|
||||
expect(subsection1aFold).not.to.be.undefined
|
||||
// The fold for subsection 1a should end on (or before) line 5 so that
|
||||
// line 6 (subsection 1b) can be folded independently.
|
||||
expect(
|
||||
view.state.doc.lineAt(subsection1aFold!.to).number
|
||||
).to.be.at.most(5)
|
||||
})
|
||||
|
||||
it('should still produce a separate fold for subsection 1b', function () {
|
||||
const folds = _getFolds(view)
|
||||
const subsection1bFold = folds.find(
|
||||
fold => view.state.doc.lineAt(fold.from).number === 6
|
||||
)
|
||||
expect(subsection1bFold).not.to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
describe('with an indented sectioning command after a blank line', function () {
|
||||
let view: EditorView, content: string[]
|
||||
|
||||
beforeEach(function () {
|
||||
// The folding algorithm should not consider whitespace on the line
|
||||
// before an indented sectioning command as part of the previous
|
||||
// section's fold.
|
||||
content = ['\\section{1}', 'Content', '', ' \\section{2}', 'Content']
|
||||
view = makeView(content)
|
||||
})
|
||||
|
||||
it('should not include the indented sectioning command line in the previous fold', function () {
|
||||
const folds = _getFolds(view)
|
||||
const firstFold = folds.find(
|
||||
fold => view.state.doc.lineAt(fold.from).number === 1
|
||||
)
|
||||
expect(firstFold).not.to.be.undefined
|
||||
expect(view.state.doc.lineAt(firstFold!.to).number).to.be.at.most(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a complex mix of comments, blank lines and indented sectioning commands', function () {
|
||||
let view: EditorView, content: string[]
|
||||
|
||||
beforeEach(function () {
|
||||
// Combines all the tricky cases:
|
||||
// - trailing `%` comment lines before indented and non-indented
|
||||
// sectioning commands
|
||||
// - blank lines (with and without trailing whitespace) before indented
|
||||
// sectioning commands
|
||||
// - multiple levels of (sub)sectioning at varying indentation
|
||||
content = [
|
||||
'\\section{1}',
|
||||
' Content',
|
||||
' \\subsection{1a}',
|
||||
' Content a',
|
||||
' %',
|
||||
' \\subsubsection{1a1a}',
|
||||
' Hello',
|
||||
' %',
|
||||
'\\subsection{1b}',
|
||||
'Content b',
|
||||
'',
|
||||
' \\subsection{1c}',
|
||||
' Content c',
|
||||
'',
|
||||
' \\subsubsection{1c1a}',
|
||||
' World',
|
||||
' ',
|
||||
' \\subsubsection{1c1b}',
|
||||
' New ',
|
||||
' ',
|
||||
]
|
||||
view = makeView(content)
|
||||
})
|
||||
|
||||
it('should produce a separate fold for each sectioning command', function () {
|
||||
const folds = _getFolds(view)
|
||||
const fromLines = folds
|
||||
.map(fold => view.state.doc.lineAt(fold.from).number)
|
||||
.sort((a, b) => a - b)
|
||||
// One fold per sectioning command line
|
||||
expect(fromLines).to.deep.equal([1, 3, 6, 9, 12, 15, 18])
|
||||
})
|
||||
|
||||
it('should not pull a sibling sectioning command line into the previous fold', function () {
|
||||
const folds = _getFolds(view)
|
||||
const foldByFromLine = new Map<number, Fold>()
|
||||
for (const fold of folds) {
|
||||
foldByFromLine.set(view.state.doc.lineAt(fold.from).number, fold)
|
||||
}
|
||||
// Each subsection 1a / 1b / 1c sibling fold must end before the next
|
||||
// subsection's line so the next subsection can be folded independently.
|
||||
const siblingPairs: Array<[number, number]> = [
|
||||
[3, 9], // \subsection{1a} fold must end before line 9 (\subsection{1b})
|
||||
[9, 12], // \subsection{1b} fold must end before line 12 (\subsection{1c})
|
||||
[15, 18], // \subsubsection{1c1a} fold must end before line 18 (\subsubsection{1c1b})
|
||||
]
|
||||
for (const [fromLine, nextFromLine] of siblingPairs) {
|
||||
const fold = foldByFromLine.get(fromLine)
|
||||
expect(fold, `fold starting on line ${fromLine}`).not.to.be.undefined
|
||||
expect(
|
||||
view.state.doc.lineAt(fold!.to).number,
|
||||
`fold starting on line ${fromLine} should end before line ${nextFromLine}`
|
||||
).to.be.lessThan(nextFromLine)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('with realistic nesting', function () {
|
||||
let view: EditorView, content: string[]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user