|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const dbginfo = new WeakMap(); |
|
8 |
|
9 // These functions implement search within the debugger. Since |
|
10 // search in the debugger is different from other components, |
|
11 // we can't use search.js CodeMirror addon. This is a slightly |
|
12 // modified version of that addon. Depends on searchcursor.js. |
|
13 |
|
14 function SearchState() { |
|
15 this.posFrom = this.posTo = this.query = null; |
|
16 } |
|
17 |
|
18 function getSearchState(cm) { |
|
19 return cm.state.search || (cm.state.search = new SearchState()); |
|
20 } |
|
21 |
|
22 function getSearchCursor(cm, query, pos) { |
|
23 // If the query string is all lowercase, do a case insensitive search. |
|
24 return cm.getSearchCursor(query, pos, |
|
25 typeof query == "string" && query == query.toLowerCase()); |
|
26 } |
|
27 |
|
28 /** |
|
29 * If there's a saved search, selects the next results. |
|
30 * Otherwise, creates a new search and selects the first |
|
31 * result. |
|
32 */ |
|
33 function doSearch(ctx, rev, query) { |
|
34 let { cm } = ctx; |
|
35 let state = getSearchState(cm); |
|
36 |
|
37 if (state.query) { |
|
38 searchNext(ctx, rev); |
|
39 return; |
|
40 } |
|
41 |
|
42 cm.operation(function () { |
|
43 if (state.query) return; |
|
44 |
|
45 state.query = query; |
|
46 state.posFrom = state.posTo = { line: 0, ch: 0 }; |
|
47 searchNext(ctx, rev); |
|
48 }); |
|
49 } |
|
50 |
|
51 /** |
|
52 * Selects the next result of a saved search. |
|
53 */ |
|
54 function searchNext(ctx, rev) { |
|
55 let { cm, ed } = ctx; |
|
56 cm.operation(function () { |
|
57 let state = getSearchState(cm) |
|
58 let cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); |
|
59 |
|
60 if (!cursor.find(rev)) { |
|
61 cursor = getSearchCursor(cm, state.query, rev ? |
|
62 { line: cm.lastLine(), ch: null } : { line: cm.firstLine(), ch: 0 }); |
|
63 if (!cursor.find(rev)) |
|
64 return; |
|
65 } |
|
66 |
|
67 ed.alignLine(cursor.from().line, "center"); |
|
68 cm.setSelection(cursor.from(), cursor.to()); |
|
69 state.posFrom = cursor.from(); |
|
70 state.posTo = cursor.to(); |
|
71 }); |
|
72 } |
|
73 |
|
74 /** |
|
75 * Clears the currently saved search. |
|
76 */ |
|
77 function clearSearch(cm) { |
|
78 let state = getSearchState(cm); |
|
79 |
|
80 if (!state.query) |
|
81 return; |
|
82 |
|
83 state.query = null; |
|
84 } |
|
85 |
|
86 // Exported functions |
|
87 |
|
88 /** |
|
89 * This function is called whenever Editor is extended with functions |
|
90 * from this module. See Editor.extend for more info. |
|
91 */ |
|
92 function initialize(ctx) { |
|
93 let { ed } = ctx; |
|
94 |
|
95 dbginfo.set(ed, { |
|
96 breakpoints: {}, |
|
97 debugLocation: null |
|
98 }); |
|
99 } |
|
100 |
|
101 /** |
|
102 * True if editor has a visual breakpoint at that line, false |
|
103 * otherwise. |
|
104 */ |
|
105 function hasBreakpoint(ctx, line) { |
|
106 let { cm } = ctx; |
|
107 let markers = cm.lineInfo(line).gutterMarkers; |
|
108 |
|
109 return markers != null && |
|
110 markers.breakpoints.classList.contains("breakpoint"); |
|
111 } |
|
112 |
|
113 /** |
|
114 * Adds a visual breakpoint for a specified line. Third |
|
115 * parameter 'cond' can hold any object. |
|
116 * |
|
117 * After adding a breakpoint, this function makes Editor to |
|
118 * emit a breakpointAdded event. |
|
119 */ |
|
120 function addBreakpoint(ctx, line, cond) { |
|
121 if (hasBreakpoint(ctx, line)) |
|
122 return; |
|
123 |
|
124 let { ed, cm } = ctx; |
|
125 let meta = dbginfo.get(ed); |
|
126 let info = cm.lineInfo(line); |
|
127 |
|
128 ed.addMarker(line, "breakpoints", "breakpoint"); |
|
129 meta.breakpoints[line] = { condition: cond }; |
|
130 |
|
131 info.handle.on("delete", function onDelete() { |
|
132 info.handle.off("delete", onDelete); |
|
133 meta.breakpoints[info.line] = null; |
|
134 }); |
|
135 |
|
136 ed.emit("breakpointAdded", line); |
|
137 } |
|
138 |
|
139 /** |
|
140 * Removes a visual breakpoint from a specified line and |
|
141 * makes Editor to emit a breakpointRemoved event. |
|
142 */ |
|
143 function removeBreakpoint(ctx, line) { |
|
144 if (!hasBreakpoint(ctx, line)) |
|
145 return; |
|
146 |
|
147 let { ed, cm } = ctx; |
|
148 let meta = dbginfo.get(ed); |
|
149 let info = cm.lineInfo(line); |
|
150 |
|
151 meta.breakpoints[info.line] = null; |
|
152 ed.removeMarker(info.line, "breakpoints", "breakpoint"); |
|
153 ed.emit("breakpointRemoved", line); |
|
154 } |
|
155 |
|
156 /** |
|
157 * Returns a list of all breakpoints in the current Editor. |
|
158 */ |
|
159 function getBreakpoints(ctx) { |
|
160 let { ed } = ctx; |
|
161 let meta = dbginfo.get(ed); |
|
162 |
|
163 return Object.keys(meta.breakpoints).reduce((acc, line) => { |
|
164 if (meta.breakpoints[line] != null) |
|
165 acc.push({ line: line, condition: meta.breakpoints[line].condition }); |
|
166 return acc; |
|
167 }, []); |
|
168 } |
|
169 |
|
170 /** |
|
171 * Saves a debug location information and adds a visual anchor to |
|
172 * the breakpoints gutter. This is used by the debugger UI to |
|
173 * display the line on which the Debugger is currently paused. |
|
174 */ |
|
175 function setDebugLocation(ctx, line) { |
|
176 let { ed } = ctx; |
|
177 let meta = dbginfo.get(ed); |
|
178 |
|
179 clearDebugLocation(ctx); |
|
180 |
|
181 meta.debugLocation = line; |
|
182 ed.addMarker(line, "breakpoints", "debugLocation"); |
|
183 ed.addLineClass(line, "debug-line"); |
|
184 } |
|
185 |
|
186 /** |
|
187 * Returns a line number that corresponds to the current debug |
|
188 * location. |
|
189 */ |
|
190 function getDebugLocation(ctx) { |
|
191 let { ed } = ctx; |
|
192 let meta = dbginfo.get(ed); |
|
193 |
|
194 return meta.debugLocation; |
|
195 } |
|
196 |
|
197 /** |
|
198 * Clears the debug location. Clearing the debug location |
|
199 * also removes a visual anchor from the breakpoints gutter. |
|
200 */ |
|
201 function clearDebugLocation(ctx) { |
|
202 let { ed } = ctx; |
|
203 let meta = dbginfo.get(ed); |
|
204 |
|
205 if (meta.debugLocation != null) { |
|
206 ed.removeMarker(meta.debugLocation, "breakpoints", "debugLocation"); |
|
207 ed.removeLineClass(meta.debugLocation, "debug-line"); |
|
208 meta.debugLocation = null; |
|
209 } |
|
210 } |
|
211 |
|
212 /** |
|
213 * Starts a new search. |
|
214 */ |
|
215 function find(ctx, query) { |
|
216 clearSearch(ctx.cm); |
|
217 doSearch(ctx, false, query); |
|
218 } |
|
219 |
|
220 /** |
|
221 * Finds the next item based on the currently saved search. |
|
222 */ |
|
223 function findNext(ctx, query) { |
|
224 doSearch(ctx, false, query); |
|
225 } |
|
226 |
|
227 /** |
|
228 * Finds the previous item based on the currently saved search. |
|
229 */ |
|
230 function findPrev(ctx, query) { |
|
231 doSearch(ctx, true, query); |
|
232 } |
|
233 |
|
234 |
|
235 // Export functions |
|
236 |
|
237 [ |
|
238 initialize, hasBreakpoint, addBreakpoint, removeBreakpoint, |
|
239 getBreakpoints, setDebugLocation, getDebugLocation, |
|
240 clearDebugLocation, find, findNext, findPrev |
|
241 ].forEach(function (func) { module.exports[func.name] = func; }); |