diff --git a/src/main.ts b/src/main.ts index ffede41b8..a6bf82e94 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,6 +25,7 @@ import {ProjectSymbolProvider} from './providers/projectsymbol' import {SectionNodeProvider} from './providers/structure' import {DefinitionProvider} from './providers/definition' import {LatexFormatterProvider} from './providers/latexformatter' +import {FoldingProvider} from './providers/folding'; function renameConfig(originalConfig: string, newConfig: string) { const configuration = vscode.workspace.getConfiguration('latex-workshop') @@ -259,6 +260,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.languages.registerCompletionItemProvider({ scheme: 'file', language: 'latex'}, extension.completer, '\\', '{', ',', '(', '[')) context.subscriptions.push(vscode.languages.registerCompletionItemProvider({ scheme: 'file', language: 'doctex'}, extension.completer, '\\', '{', ',', '(', '[')) context.subscriptions.push(vscode.languages.registerCodeActionsProvider({ scheme: 'file', language: 'latex'}, extension.codeActions)) + context.subscriptions.push(vscode.languages.registerFoldingRangeProvider({ scheme: 'file', language: 'latex'}, new FoldingProvider())) extension.linter.lintRootFileIfEnabled() obsoleteConfigCheck() diff --git a/src/providers/folding.ts b/src/providers/folding.ts new file mode 100644 index 000000000..409452420 --- /dev/null +++ b/src/providers/folding.ts @@ -0,0 +1,82 @@ +import * as vscode from 'vscode' + +export class FoldingProvider implements vscode.FoldingRangeProvider { + + public provideFoldingRanges( + document: vscode.TextDocument, + _context: vscode.FoldingContext, + _token: vscode.CancellationToken + ): vscode.ProviderResult { + return [...this.getSectionFoldingRanges(document), ...this.getEnvironmentFoldingRanges(document)] + } + + private getSectionFoldingRanges(document: vscode.TextDocument) { + let lines = document.getText().split(/\r?\n/g) + + let sections = lines.map((lineText, lineNumber) => { + const sectionRegex = /^\\((?:sub)*)section{.*}/ + let matches + if (matches = sectionRegex.exec(lineText)) { + let section = {} + section["level"] = matches[1].length / 3 + 1 + section["lineNumber"] = lineNumber + return section + } else { + return {} + } + }) + + return sections.filter(section => section["level"]).map((section, index, sections) => { + let startLine = section["lineNumber"] + let endLine + if (index < sections.length - 1) { // Not the last section + for (let siblingSectionIndex = index + 1; siblingSectionIndex < sections.length; siblingSectionIndex++) { + if (section["level"] >= sections[siblingSectionIndex]["level"]) { + endLine = sections[siblingSectionIndex]["lineNumber"] - 1 + break + } + } + } else { + endLine = document.lineCount - 1 + for (; endLine > startLine; endLine--) { + if (/\\end{document}/.test(document.lineAt(endLine).text)) { + endLine-- + break + } + } + } + + if (document.lineAt(endLine).isEmptyOrWhitespace && endLine >= section["lineNumber"] + 1) { + endLine = endLine - 1; + } + + return new vscode.FoldingRange(startLine, endLine) + }) + } + + private getEnvironmentFoldingRanges(document: vscode.TextDocument) { + let ranges: vscode.FoldingRange[] = [] + let textToMatch = [{ text: document.getText(), offset: 0 }] + while (textToMatch.length > 0) { + let newTextToMatch: { text: string, offset: number }[] = [] + textToMatch.forEach(textObj => { + const envRegex = /(\\begin{(.*?)})([\w\W]*)\\end{\2}/g; + let match + while (match = envRegex.exec(textObj.text)) { + ranges.push( + new vscode.FoldingRange( + document.positionAt(textObj.offset + envRegex.lastIndex - match[0].length).line, + document.positionAt(textObj.offset + envRegex.lastIndex).line - 1 + ) + ) + newTextToMatch.push({ + text: match[3], + offset: textObj.offset + envRegex.lastIndex - match[0].length + match[1].length + }) + } + }) + textToMatch = newTextToMatch + } + return ranges + } +} \ No newline at end of file