1/*  DokuWiki MoaiEditor Matches.js file
2    Version : 0.5 (May 5, 2026)
3    Author  : MoaiTools <info@moaitools.org>
4    License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */
5
6MoaiEditor.Matches = class {
7
8    constructor(outer) {
9
10        // Handles
11
12        // Settings
13        this.settings = {show:{scrollpoints:false}};
14
15        // Variables
16        this.matches = [];          // array of {type, id, handle, startline, endline, syntax, scroll:{top,bottom}}
17        this.newMatches = [];       // newly discovered matches after a preview update (equal to 'this.matches' if partial preview is off)
18        this.startline = 0;         // First starting line to be parsed after the preview response has been received
19
20        // Objects
21        this.matcher = new MoaiEditor.Matcher(this);
22    }
23    // ┌───────────────────────────────────┐
24    // │ Public                            │
25    // └───────────────────────────────────┘
26
27    add (type, handle, startline, endline, syntax=null) {
28        const match = {
29            type      : type,
30            handle    : handle,
31            id        : handle.id,
32            startline : this.startline + startline,
33            endline   : this.startline + endline,
34            syntax    : syntax,
35            scroll    : null,
36        };
37        this.matches.push(match);           // Global list of matches
38        this.newMatches.push(match);        // Only new matches
39    }
40    // ────────────────────────────────────
41    update(top, bottom) {
42        this.start = Date.now();
43
44        // Get current editor
45        const editor = moaiEditor.editor.current;
46
47        // Get the starting and ending line
48        var startline = 0;
49        if (top !== null)
50            startline = top.endline + 1;
51        this.startline = startline;
52        var endline = editor.watcher.lines.length-1;
53        if (bottom !== null)
54            endline = bottom.startline-1;
55
56        // Remove matches in the changed area
57        this.removeMatches(startline, endline, true);
58
59        // Run matcher
60        this.newMatches = [];
61        this.matcher.onAjax (top, bottom, startline, endline);
62
63        // Sort matches by start line
64        this.matches.sort((a, b) => (a.startline > b.startline) ? 1 : -1);
65
66        // Let the editor process the newly found matches
67        var start = Date.now();
68        editor.onAjax(this.newMatches);
69
70        // Calc scroll points of new matches
71        for (let match of this.newMatches) {
72            const top = editor.getLineRect(match.startline).top;
73            const bottom = editor.getLineRect(match.endline).bottom;
74            match.scroll = {top:top, bottom:bottom};
75        }
76        // Show scrollpoints (for debug)
77        this.showScrollPoints('onAjax');
78
79        const elapsed = Date.now() - start;
80        this.measureFrameTime("�� MATCHES.ONAJAX (FRAME TIME)");
81    }
82    // ────────────────────────────────────
83    onTextChanged(change) {
84
85        // Get current editor
86        const editor = moaiEditor.editor.current;
87
88        // Remove matches in the changed area
89        var keep = [];
90        const start = change.num.keepfirst;
91        const end = change.index.keeplast-1;
92        this.removeMatches(start, end);
93
94        // Shift the line-numbers and scroll-points of the matches (below the changed zone)
95        var shift = null;
96        for (let match of this.matches)
97            if (match.startline >= change.index.keeplast) {
98                // Shift line numbers
99                match.startline += change.shift;
100                match.endline += change.shift;
101                // Shift the scroll positions
102                if (match.scroll !== null) {
103                    if (shift === null)
104                        shift = editor.getLineRect(match.startline).top - match.scroll.top;
105                    match.scroll.top += shift;
106                    match.scroll.bottom += shift;
107                }
108            }
109        // Show scrollpoints (for debug)
110        this.showScrollPoints('onTextChanged');
111    }
112    // ────────────────────────────────────
113    removeMatches(startline, endline, editor=false) {
114        var keep = [];
115        for (let match of this.matches)
116            if (!this.collides(match, startline, endline))
117                keep.push(match);
118            else if (editor)
119                moaiEditor.editor.current.removeMatches(match.startline, match.endline);
120        this.matches = keep;
121    }
122    // ────────────────────────────────────
123    collides(match, start, end) {
124        if (match.startline >= start  &&  match.startline <= end)
125            return true;
126        if (match.endline >= start  &&  match.endline <= end)
127            return true;
128        if (start >= match.startline &&  start <= match.endline)
129            return true;
130        if (end >= match.startline &&  end <= match.endline)
131            return true;
132        return false;
133    }
134    // ────────────────────────────────────
135    find(handle) {
136        for (let match of this.matches)
137            if (match.handle == handle)
138                return match;
139        return null;
140    }
141    // ────────────────────────────────────
142    recalcScrollPoints(range=null) {
143        /* @range can be:
144         *   null       : to recalculate all scroll points (called when the editor size or font changes)
145         *   {from, to} : to recalculate scroll points in a range of lines (called when CodeMirror viewport changes)
146         */
147        this.start = Date.now();
148        const editor  = moaiEditor.editor.current;
149        for (let match of this.matches) {
150            if ( range !== null  &&  !this.collides(match, range.from, range.to) )
151                continue;
152            const top = editor.getLineRect(match.startline).top;
153            const bottom = editor.getLineRect(match.endline).bottom;
154            match.scroll = {top:top, bottom:bottom};
155        }
156    }
157    // ────────────────────────────────────
158    showScrollPoints(caller) {
159        if (!this.settings.show.scrollpoints)
160            return;
161        const container = document.querySelector("#moaied__scrollpoints_overlay");
162        container.style.display = 'block';
163        container.innerHTML = '';
164        const editor  = moaiEditor.editor.current;
165        for (let match of this.matches) {
166            const top = editor.getLineRect(match.startline).top;
167            const bottom = editor.getLineRect(match.endline).bottom;
168            var element = moaiEditor.createHTML('<div class="moaied-scrollpoint"></div>');
169            element.style.top =  match.scroll.top+'px';
170            element.style.height = (match.scroll.bottom - match.scroll.top)+'px';
171            container.appendChild(element);
172        }
173    }
174    // ────────────────────────────────────
175    measureFrameTime (text) {
176        requestAnimationFrame(() => {
177            const elapsed = Date.now()-this.start;
178        });
179    }
180}; // End Class
181
182
183
184
185
186
187