// Search module
// Requires: editor, openFile, getFileIcon, setStatus, escapeHtml, escapeAttr from global scope
function toggleSearchPanel() {
const panel = document.getElementById('search-panel');
if (panel.classList.contains('hidden')) {
panel.classList.remove('hidden');
document.getElementById('search-input').focus();
} else {
panel.classList.add('hidden');
}
}
function closeSearchPanel() {
document.getElementById('search-panel').classList.add('hidden');
}
async function performSearch() {
const query = document.getElementById('search-input').value.trim();
if (!query) return;
const isRegex = document.getElementById('regex-toggle').checked;
const resultsDiv = document.getElementById('search-results');
resultsDiv.innerHTML = '<div class="p-3 text-sm text-base-content/50">Searching...</div>';
try {
const params = new URLSearchParams({ q: query });
if (isRegex) params.append('regex', 'true');
const res = await fetch(`/api/search?${params}`);
if (!res.ok) {
const err = await res.json();
const errorDiv = document.createElement('div');
errorDiv.className = 'p-3 text-sm text-error';
errorDiv.textContent = err.error || 'Search failed';
resultsDiv.innerHTML = '';
resultsDiv.appendChild(errorDiv);
return;
}
const results = await res.json();
if (results.length === 0) {
resultsDiv.innerHTML = '<div class="p-3 text-sm text-base-content/50">No results found</div>';
return;
}
// Group results by file
const grouped = {};
results.forEach(r => {
if (!grouped[r.path]) grouped[r.path] = [];
grouped[r.path].push(r);
});
let html = '';
for (const [path, matches] of Object.entries(grouped)) {
html += `
<div class="border-b border-base-100">
<div class="px-3 py-2 bg-base-200/50 text-xs font-semibold flex items-center gap-1">
<span>${getFileIcon(path)}</span>
<span>${path}</span>
<span class="text-base-content/50">(${matches.length})</span>
</div>
<ul class="menu menu-xs p-1">
`;
matches.forEach(m => {
html += `
<li>
<a onclick="goToSearchResult('${escapeAttr(path)}', ${m.line}, ${m.column})" class="py-1">
<span class="text-base-content/50 w-8 text-right">${m.line}</span>
<span class="font-mono text-xs truncate">${escapeHtml(m.context)}</span>
</a>
</li>
`;
});
html += '</ul></div>';
}
resultsDiv.innerHTML = html;
setStatus(`Found ${results.length} results in ${Object.keys(grouped).length} files`);
} catch (err) {
resultsDiv.innerHTML = '<div class="p-3 text-sm text-error">Search failed</div>';
}
}
async function goToSearchResult(path, line, column) {
await openFile(path);
editor.revealLineInCenter(line);
editor.setPosition({ lineNumber: line, column: column });
editor.focus();
}
// Keyboard shortcut for search (Ctrl+Shift+F)
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'F') {
e.preventDefault();
toggleSearchPanel();
}
});