monaco-latex
Version:
LaTeX language support for Monaco Editor
136 lines (119 loc) • 4.99 kB
text/typescript
/**
* LaTeX Folding Range Provider for Monaco Editor.
* Provides code folding for LaTeX environments and sections.
*/
import type * as Monaco from 'monaco-editor';
export class LaTeXFoldingRangeProvider implements Monaco.languages.FoldingRangeProvider {
constructor(private monaco: typeof Monaco) {}
provideFoldingRanges(
model: Monaco.editor.ITextModel,
context: Monaco.languages.FoldingContext,
token: Monaco.CancellationToken
): Monaco.languages.ProviderResult<Monaco.languages.FoldingRange[]> {
const ranges: Monaco.languages.FoldingRange[] = [];
const lineCount = model.getLineCount();
// Stack for tracking nested environments
const envStack: { name: string; line: number }[] = [];
// Stack for sections (hierarchical)
const sectionStack: { level: number; line: number }[] = [];
const sectionLevels: Record<string, number> = {
part: 0,
chapter: 1,
section: 2,
subsection: 3,
subsubsection: 4,
paragraph: 5,
subparagraph: 6,
};
// Multi-line comment tracking
let commentStart: number | null = null;
for (let lineNumber = 1; lineNumber <= lineCount; lineNumber++) {
const line = model.getLineContent(lineNumber);
// Check for \begin{environment}
const beginMatch = line.match(/\\begin\{(\w+\*?)\}/);
if (beginMatch) {
envStack.push({ name: beginMatch[1], line: lineNumber });
}
// Check for \end{environment}
const endMatch = line.match(/\\end\{(\w+\*?)\}/);
if (endMatch && envStack.length > 0) {
// Find matching begin
for (let i = envStack.length - 1; i >= 0; i--) {
if (envStack[i].name === endMatch[1]) {
if (lineNumber > envStack[i].line) {
ranges.push({
start: envStack[i].line,
end: lineNumber,
kind: this.monaco.languages.FoldingRangeKind.Region,
});
}
envStack.splice(i, 1);
break;
}
}
}
// Check for sections
const sectionMatch = line.match(
/\\(part|chapter|section|subsection|subsubsection|paragraph|subparagraph)\*?\s*[\[{]/
);
if (sectionMatch) {
const level = sectionLevels[sectionMatch[1]];
// Close any sections at same or higher level (lower number = higher level)
while (
sectionStack.length > 0 &&
sectionStack[sectionStack.length - 1].level >= level
) {
const prev = sectionStack.pop()!;
if (lineNumber - prev.line > 1) {
ranges.push({
start: prev.line,
end: lineNumber - 1,
kind: this.monaco.languages.FoldingRangeKind.Region,
});
}
}
sectionStack.push({ level, line: lineNumber });
}
// Check for comment blocks (consecutive comment lines)
const isComment = line.trim().startsWith('%');
if (isComment && commentStart === null) {
commentStart = lineNumber;
} else if (!isComment && commentStart !== null) {
if (lineNumber - commentStart > 2) {
ranges.push({
start: commentStart,
end: lineNumber - 1,
kind: this.monaco.languages.FoldingRangeKind.Comment,
});
}
commentStart = null;
}
}
// Close remaining sections at end of document
while (sectionStack.length > 0) {
const prev = sectionStack.pop()!;
if (lineCount - prev.line > 1) {
ranges.push({
start: prev.line,
end: lineCount,
kind: this.monaco.languages.FoldingRangeKind.Region,
});
}
}
// Close any unclosed comment block
if (commentStart !== null && lineCount - commentStart > 2) {
ranges.push({
start: commentStart,
end: lineCount,
kind: this.monaco.languages.FoldingRangeKind.Comment,
});
}
return ranges;
}
}
/**
* Creates a folding range provider for LaTeX.
*/
export function createFoldingProvider(monaco: typeof Monaco): LaTeXFoldingRangeProvider {
return new LaTeXFoldingRangeProvider(monaco);
}