UNPKG

monaco-editor

Version:
129 lines (126 loc) 5.89 kB
import { isMultilineRegexSource } from '../model/textModelSearch.js'; import { regExpLeadsToEndlessLoop } from '../../../base/common/strings.js'; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const trimDashesRegex = /^-+|-+$/g; const CHUNK_SIZE = 100; const MAX_SECTION_LINES = 5; /** * Find section headers in the model. * * @param model the text model to search in * @param options options to search with * @returns an array of section headers */ function findSectionHeaders(model, options) { let headers = []; if (options.findRegionSectionHeaders && options.foldingRules?.markers) { const regionHeaders = collectRegionHeaders(model, options); headers = headers.concat(regionHeaders); } if (options.findMarkSectionHeaders) { const markHeaders = collectMarkHeaders(model, options); headers = headers.concat(markHeaders); } return headers; } function collectRegionHeaders(model, options) { const regionHeaders = []; const endLineNumber = model.getLineCount(); for (let lineNumber = 1; lineNumber <= endLineNumber; lineNumber++) { const lineContent = model.getLineContent(lineNumber); const match = lineContent.match(options.foldingRules.markers.start); if (match) { const range = { startLineNumber: lineNumber, startColumn: match[0].length + 1, endLineNumber: lineNumber, endColumn: lineContent.length + 1 }; if (range.endColumn > range.startColumn) { const sectionHeader = { range, ...getHeaderText(lineContent.substring(match[0].length)), shouldBeInComments: false }; if (sectionHeader.text || sectionHeader.hasSeparatorLine) { regionHeaders.push(sectionHeader); } } } } return regionHeaders; } function collectMarkHeaders(model, options) { const markHeaders = []; const endLineNumber = model.getLineCount(); // Validate regex to prevent infinite loops if (!options.markSectionHeaderRegex || options.markSectionHeaderRegex.trim() === '') { return markHeaders; } // Create regex with flags for: // - 'd' for indices to get proper match positions // - 'm' for multi-line mode so ^ and $ match line starts/ends // - 's' for dot-all mode so . matches newlines const multiline = isMultilineRegexSource(options.markSectionHeaderRegex); const regex = new RegExp(options.markSectionHeaderRegex, `gdm${multiline ? 's' : ''}`); // Check if the regex would lead to an endless loop if (regExpLeadsToEndlessLoop(regex)) { return markHeaders; } // Process text in overlapping chunks for better performance for (let startLine = 1; startLine <= endLineNumber; startLine += CHUNK_SIZE - MAX_SECTION_LINES) { const endLine = Math.min(startLine + CHUNK_SIZE - 1, endLineNumber); const lines = []; // Collect lines for the current chunk for (let i = startLine; i <= endLine; i++) { lines.push(model.getLineContent(i)); } const text = lines.join('\n'); regex.lastIndex = 0; let match; while ((match = regex.exec(text)) !== null) { // Calculate which line this match starts on by counting newlines before it const precedingText = text.substring(0, match.index); const lineOffset = (precedingText.match(/\n/g) || []).length; const lineNumber = startLine + lineOffset; // Calculate match height to check overlap properly const matchLines = match[0].split('\n'); const matchHeight = matchLines.length; const matchEndLine = lineNumber + matchHeight - 1; // Calculate start column - need to find the start of the line containing the match const lineStartIndex = precedingText.lastIndexOf('\n') + 1; const startColumn = match.index - lineStartIndex + 1; // Calculate end column - need to handle multi-line matches const lastMatchLine = matchLines[matchLines.length - 1]; const endColumn = matchHeight === 1 ? startColumn + match[0].length : lastMatchLine.length + 1; const range = { startLineNumber: lineNumber, startColumn, endLineNumber: matchEndLine, endColumn }; const text2 = (match.groups ?? {})['label'] ?? ''; const hasSeparatorLine = ((match.groups ?? {})['separator'] ?? '') !== ''; const sectionHeader = { range, text: text2, hasSeparatorLine, shouldBeInComments: true }; if (sectionHeader.text || sectionHeader.hasSeparatorLine) { // only push if the previous one doesn't have this same linbe if (markHeaders.length === 0 || markHeaders[markHeaders.length - 1].range.endLineNumber < sectionHeader.range.startLineNumber) { markHeaders.push(sectionHeader); } } // Move lastIndex past the current match to avoid infinite loop regex.lastIndex = match.index + match[0].length; } } return markHeaders; } function getHeaderText(text) { text = text.trim(); const hasSeparatorLine = text.startsWith('-'); text = text.replace(trimDashesRegex, ''); return { text, hasSeparatorLine }; } export { collectMarkHeaders, findSectionHeaders };