// fold-finder.js - Intelligent code fold detection for multiple languages
const FoldFinder = (() => {
// Utility functions
function normalize(str) {
return str.replace(/\s+/g, ' ').trim();
}
function findMatchingPair(text, open, close, start) {
let depth = 1;
for (let i = start + 1; i < text.length; i++) {
if (text[i] === open) depth++;
if (text[i] === close) {
depth--;
if (depth === 0) return i;
}
}
return -1;
}
function calculateMatchRatio(foldText, searchText) {
const normFold = normalize(foldText);
const normSearch = normalize(searchText);
const searchTokens = normSearch.split(' ').filter(t => t.length > 2);
const matchCount = searchTokens.filter(tok => normFold.includes(tok)).length;
return searchTokens.length > 0 ? matchCount / searchTokens.length : 0;
}
// Language detection
function detectLanguage(text) {
text = text.trim();
if (/^\s*<\?php/i.test(text) || (/function\s+\w+\s*\([^)]*\)\s*\{/i.test(text) && /\$/i.test(text))) {
return 'php';
}
if (/^\s*<[a-z]+[\s>]/i.test(text) || /<\/[a-z]+>/i.test(text)) {
return 'html';
}
if (/^\s*def\s+\w+/i.test(text)) {
return 'python';
}
if (/^\s*(function|const|let|var|class)\s+/i.test(text)) {
return 'js';
}
return 'js'; // default
}
// JavaScript fold finder
function findJsFold(text, pastedText) {
const normSearch = normalize(pastedText);
const sigMatch = pastedText.match(/(function\s+\w+|class\s+\w+|const\s+\w+\s*=|let\s+\w+\s*=|var\s+\w+\s*=|if\s*\(|for\s*\(|while\s*\()/i);
if (!sigMatch) return null;
const signature = sigMatch[0];
const lines = text.split('\n');
const results = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes(signature)) {
const lineStart = lines.slice(0, i).join('\n').length + (i > 0 ? 1 : 0);
const braceIdx = text.indexOf('{', lineStart);
if (braceIdx !== -1) {
const braceEnd = findMatchingPair(text, '{', '}', braceIdx);
if (braceEnd !== -1) {
const foldText = text.slice(lineStart, braceEnd + 1);
const matchRatio = calculateMatchRatio(foldText, pastedText);
if (matchRatio > 0.5) {
results.push({
start: lineStart,
end: braceEnd + 1,
row: i,
matchRatio: matchRatio
});
}
}
}
}
}
results.sort((a, b) => b.matchRatio - a.matchRatio);
return results[0] || null;
}
// PHP fold finder
function findPhpFold(text, pastedText) {
const normSearch = normalize(pastedText);
const hasPhpTag = /^\s*<\?php/i.test(pastedText);
const sigMatch = pastedText.match(/(function\s+\w+|class\s+\w+)/i);
if (sigMatch) {
const signature = sigMatch[0];
const lines = text.split('\n');
const results = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes(signature)) {
const lineStart = lines.slice(0, i).join('\n').length + (i > 0 ? 1 : 0);
const braceIdx = text.indexOf('{', lineStart);
if (braceIdx !== -1) {
const braceEnd = findMatchingPair(text, '{', '}', braceIdx);
if (braceEnd !== -1) {
const foldText = text.slice(lineStart, braceEnd + 1);
const matchRatio = calculateMatchRatio(foldText, pastedText);
if (matchRatio > 0.5) {
results.push({
start: lineStart,
end: braceEnd + 1,
row: i,
matchRatio: matchRatio
});
}
}
}
}
}
results.sort((a, b) => b.matchRatio - a.matchRatio);
if (results[0]) return results[0];
}
// Fall back to PHP block
if (hasPhpTag) {
const phpOpenIdx = text.indexOf('<?php');
if (phpOpenIdx !== -1) {
let phpCloseIdx = text.indexOf('?>', phpOpenIdx);
if (phpCloseIdx === -1) phpCloseIdx = text.length;
else phpCloseIdx += 2;
const beforeStart = text.slice(0, phpOpenIdx);
const row = (beforeStart.match(/\n/g) || []).length;
return {
start: phpOpenIdx,
end: phpCloseIdx,
row: row,
matchRatio: 0.7
};
}
}
return null;
}
// HTML fold finder
function findHtmlFold(text, pastedText) {
const normSearch = normalize(pastedText);
const tagMatch = pastedText.match(/<([a-zA-Z0-9-]+)[\s>]/i);
if (!tagMatch) return null;
const tagName = tagMatch[1];
const lines = text.split('\n');
const results = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const openPattern = new RegExp('<' + tagName + '[\\s>]', 'i');
if (openPattern.test(line)) {
const lineStart = lines.slice(0, i).join('\n').length + (i > 0 ? 1 : 0);
const tagStart = text.indexOf('<' + tagName, lineStart);
// Find closing tag with depth tracking
let depth = 1;
let searchPos = tagStart + tagName.length + 1;
while (depth > 0 && searchPos < text.length) {
const nextOpen = text.indexOf('<' + tagName, searchPos);
const nextClose = text.indexOf('</' + tagName + '>', searchPos);
if (nextClose === -1) break;
if (nextOpen !== -1 && nextOpen < nextClose) {
depth++;
searchPos = nextOpen + tagName.length + 1;
} else {
depth--;
if (depth === 0) {
const closeEnd = nextClose + tagName.length + 3;
const foldText = text.slice(tagStart, closeEnd);
const matchRatio = calculateMatchRatio(foldText, pastedText);
if (matchRatio > 0.4) {
results.push({
start: tagStart,
end: closeEnd,
row: i,
matchRatio: matchRatio
});
}
break;
}
searchPos = nextClose + tagName.length + 3;
}
}
}
}
results.sort((a, b) => b.matchRatio - a.matchRatio);
return results[0] || null;
}
// Python fold finder
function findPythonFold(text, pastedText) {
const normSearch = normalize(pastedText);
const sigMatch = pastedText.match(/(def\s+\w+|class\s+\w+)/i);
if (!sigMatch) return null;
const signature = sigMatch[0];
const lines = text.split('\n');
const results = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes(signature)) {
const baseIndent = line.match(/^\s*/)[0].length;
let endRow = i;
// Find end of function by indentation
for (let j = i + 1; j < lines.length; j++) {
const jLine = lines[j];
if (jLine.trim() === '') continue;
const jIndent = jLine.match(/^\s*/)[0].length;
if (jIndent <= baseIndent) {
endRow = j - 1;
break;
}
endRow = j;
}
const startPos = lines.slice(0, i).join('\n').length + (i > 0 ? 1 : 0);
const endPos = lines.slice(0, endRow + 1).join('\n').length;
const foldText = text.slice(startPos, endPos);
const matchRatio = calculateMatchRatio(foldText, pastedText);
if (matchRatio > 0.5) {
results.push({
start: startPos,
end: endPos,
row: i,
matchRatio: matchRatio
});
}
}
}
results.sort((a, b) => b.matchRatio - a.matchRatio);
return results[0] || null;
}
// Main find function
function findFold(text, pastedText, lang) {
switch (lang) {
case 'php': return findPhpFold(text, pastedText);
case 'html': return findHtmlFold(text, pastedText);
case 'python': return findPythonFold(text, pastedText);
case 'js':
default: return findJsFold(text, pastedText);
}
}
// Public API
return {
detectLanguage,
findFold,
findJsFold,
findPhpFold,
findHtmlFold,
findPythonFold
};
})();