mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-18 21:57:44 +01:00
117 lines
3.5 KiB
TypeScript
117 lines
3.5 KiB
TypeScript
import { getSearchQuery, searchPanelOpen } from '@codemirror/search';
|
|
import type { Extension } from '@codemirror/state';
|
|
import { type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
|
|
|
/**
|
|
* A CodeMirror extension that displays the total number of search matches
|
|
* inside the built-in search panel.
|
|
*/
|
|
export function searchMatchCount(): Extension {
|
|
return ViewPlugin.fromClass(
|
|
class {
|
|
private countEl: HTMLElement | null = null;
|
|
|
|
constructor(private view: EditorView) {
|
|
this.updateCount();
|
|
}
|
|
|
|
update(update: ViewUpdate) {
|
|
// Recompute when doc changes, search state changes, or selection moves
|
|
const query = getSearchQuery(update.state);
|
|
const prevQuery = getSearchQuery(update.startState);
|
|
const open = searchPanelOpen(update.state);
|
|
const prevOpen = searchPanelOpen(update.startState);
|
|
|
|
if (update.docChanged || update.selectionSet || !query.eq(prevQuery) || open !== prevOpen) {
|
|
this.updateCount();
|
|
}
|
|
}
|
|
|
|
private updateCount() {
|
|
const state = this.view.state;
|
|
const open = searchPanelOpen(state);
|
|
const query = getSearchQuery(state);
|
|
|
|
if (!open) {
|
|
this.removeCountEl();
|
|
return;
|
|
}
|
|
|
|
this.ensureCountEl();
|
|
|
|
if (!query.search) {
|
|
if (this.countEl) {
|
|
this.countEl.textContent = '0/0';
|
|
}
|
|
return;
|
|
}
|
|
|
|
const selection = state.selection.main;
|
|
let count = 0;
|
|
let currentIndex = 0;
|
|
const MAX_COUNT = 9999;
|
|
const cursor = query.getCursor(state);
|
|
for (let result = cursor.next(); !result.done; result = cursor.next()) {
|
|
count++;
|
|
const match = result.value;
|
|
if (match.from <= selection.from && match.to >= selection.to) {
|
|
currentIndex = count;
|
|
}
|
|
if (count > MAX_COUNT) break;
|
|
}
|
|
|
|
if (this.countEl) {
|
|
if (count > MAX_COUNT) {
|
|
this.countEl.textContent = `${MAX_COUNT}+`;
|
|
} else if (count === 0) {
|
|
this.countEl.textContent = '0/0';
|
|
} else if (currentIndex > 0) {
|
|
this.countEl.textContent = `${currentIndex}/${count}`;
|
|
} else {
|
|
this.countEl.textContent = `0/${count}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
private ensureCountEl() {
|
|
// Find the search panel in the editor DOM
|
|
const panel = this.view.dom.querySelector('.cm-search');
|
|
if (!panel) {
|
|
this.countEl = null;
|
|
return;
|
|
}
|
|
|
|
if (this.countEl && this.countEl.parentElement === panel) {
|
|
return; // Already attached
|
|
}
|
|
|
|
this.countEl = document.createElement('span');
|
|
this.countEl.className = 'cm-search-match-count';
|
|
|
|
// Reorder: insert prev button, then next button, then count after the search input
|
|
const searchInput = panel.querySelector('input');
|
|
const prevBtn = panel.querySelector('button[name="prev"]');
|
|
const nextBtn = panel.querySelector('button[name="next"]');
|
|
if (searchInput && searchInput.parentElement === panel) {
|
|
searchInput.after(this.countEl);
|
|
if (prevBtn) this.countEl.after(prevBtn);
|
|
if (nextBtn && prevBtn) prevBtn.after(nextBtn);
|
|
} else {
|
|
panel.prepend(this.countEl);
|
|
}
|
|
}
|
|
|
|
private removeCountEl() {
|
|
if (this.countEl) {
|
|
this.countEl.remove();
|
|
this.countEl = null;
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
this.removeCountEl();
|
|
}
|
|
},
|
|
);
|
|
}
|