michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const dbginfo = new WeakMap(); michael@0: michael@0: // These functions implement search within the debugger. Since michael@0: // search in the debugger is different from other components, michael@0: // we can't use search.js CodeMirror addon. This is a slightly michael@0: // modified version of that addon. Depends on searchcursor.js. michael@0: michael@0: function SearchState() { michael@0: this.posFrom = this.posTo = this.query = null; michael@0: } michael@0: michael@0: function getSearchState(cm) { michael@0: return cm.state.search || (cm.state.search = new SearchState()); michael@0: } michael@0: michael@0: function getSearchCursor(cm, query, pos) { michael@0: // If the query string is all lowercase, do a case insensitive search. michael@0: return cm.getSearchCursor(query, pos, michael@0: typeof query == "string" && query == query.toLowerCase()); michael@0: } michael@0: michael@0: /** michael@0: * If there's a saved search, selects the next results. michael@0: * Otherwise, creates a new search and selects the first michael@0: * result. michael@0: */ michael@0: function doSearch(ctx, rev, query) { michael@0: let { cm } = ctx; michael@0: let state = getSearchState(cm); michael@0: michael@0: if (state.query) { michael@0: searchNext(ctx, rev); michael@0: return; michael@0: } michael@0: michael@0: cm.operation(function () { michael@0: if (state.query) return; michael@0: michael@0: state.query = query; michael@0: state.posFrom = state.posTo = { line: 0, ch: 0 }; michael@0: searchNext(ctx, rev); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Selects the next result of a saved search. michael@0: */ michael@0: function searchNext(ctx, rev) { michael@0: let { cm, ed } = ctx; michael@0: cm.operation(function () { michael@0: let state = getSearchState(cm) michael@0: let cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); michael@0: michael@0: if (!cursor.find(rev)) { michael@0: cursor = getSearchCursor(cm, state.query, rev ? michael@0: { line: cm.lastLine(), ch: null } : { line: cm.firstLine(), ch: 0 }); michael@0: if (!cursor.find(rev)) michael@0: return; michael@0: } michael@0: michael@0: ed.alignLine(cursor.from().line, "center"); michael@0: cm.setSelection(cursor.from(), cursor.to()); michael@0: state.posFrom = cursor.from(); michael@0: state.posTo = cursor.to(); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Clears the currently saved search. michael@0: */ michael@0: function clearSearch(cm) { michael@0: let state = getSearchState(cm); michael@0: michael@0: if (!state.query) michael@0: return; michael@0: michael@0: state.query = null; michael@0: } michael@0: michael@0: // Exported functions michael@0: michael@0: /** michael@0: * This function is called whenever Editor is extended with functions michael@0: * from this module. See Editor.extend for more info. michael@0: */ michael@0: function initialize(ctx) { michael@0: let { ed } = ctx; michael@0: michael@0: dbginfo.set(ed, { michael@0: breakpoints: {}, michael@0: debugLocation: null michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * True if editor has a visual breakpoint at that line, false michael@0: * otherwise. michael@0: */ michael@0: function hasBreakpoint(ctx, line) { michael@0: let { cm } = ctx; michael@0: let markers = cm.lineInfo(line).gutterMarkers; michael@0: michael@0: return markers != null && michael@0: markers.breakpoints.classList.contains("breakpoint"); michael@0: } michael@0: michael@0: /** michael@0: * Adds a visual breakpoint for a specified line. Third michael@0: * parameter 'cond' can hold any object. michael@0: * michael@0: * After adding a breakpoint, this function makes Editor to michael@0: * emit a breakpointAdded event. michael@0: */ michael@0: function addBreakpoint(ctx, line, cond) { michael@0: if (hasBreakpoint(ctx, line)) michael@0: return; michael@0: michael@0: let { ed, cm } = ctx; michael@0: let meta = dbginfo.get(ed); michael@0: let info = cm.lineInfo(line); michael@0: michael@0: ed.addMarker(line, "breakpoints", "breakpoint"); michael@0: meta.breakpoints[line] = { condition: cond }; michael@0: michael@0: info.handle.on("delete", function onDelete() { michael@0: info.handle.off("delete", onDelete); michael@0: meta.breakpoints[info.line] = null; michael@0: }); michael@0: michael@0: ed.emit("breakpointAdded", line); michael@0: } michael@0: michael@0: /** michael@0: * Removes a visual breakpoint from a specified line and michael@0: * makes Editor to emit a breakpointRemoved event. michael@0: */ michael@0: function removeBreakpoint(ctx, line) { michael@0: if (!hasBreakpoint(ctx, line)) michael@0: return; michael@0: michael@0: let { ed, cm } = ctx; michael@0: let meta = dbginfo.get(ed); michael@0: let info = cm.lineInfo(line); michael@0: michael@0: meta.breakpoints[info.line] = null; michael@0: ed.removeMarker(info.line, "breakpoints", "breakpoint"); michael@0: ed.emit("breakpointRemoved", line); michael@0: } michael@0: michael@0: /** michael@0: * Returns a list of all breakpoints in the current Editor. michael@0: */ michael@0: function getBreakpoints(ctx) { michael@0: let { ed } = ctx; michael@0: let meta = dbginfo.get(ed); michael@0: michael@0: return Object.keys(meta.breakpoints).reduce((acc, line) => { michael@0: if (meta.breakpoints[line] != null) michael@0: acc.push({ line: line, condition: meta.breakpoints[line].condition }); michael@0: return acc; michael@0: }, []); michael@0: } michael@0: michael@0: /** michael@0: * Saves a debug location information and adds a visual anchor to michael@0: * the breakpoints gutter. This is used by the debugger UI to michael@0: * display the line on which the Debugger is currently paused. michael@0: */ michael@0: function setDebugLocation(ctx, line) { michael@0: let { ed } = ctx; michael@0: let meta = dbginfo.get(ed); michael@0: michael@0: clearDebugLocation(ctx); michael@0: michael@0: meta.debugLocation = line; michael@0: ed.addMarker(line, "breakpoints", "debugLocation"); michael@0: ed.addLineClass(line, "debug-line"); michael@0: } michael@0: michael@0: /** michael@0: * Returns a line number that corresponds to the current debug michael@0: * location. michael@0: */ michael@0: function getDebugLocation(ctx) { michael@0: let { ed } = ctx; michael@0: let meta = dbginfo.get(ed); michael@0: michael@0: return meta.debugLocation; michael@0: } michael@0: michael@0: /** michael@0: * Clears the debug location. Clearing the debug location michael@0: * also removes a visual anchor from the breakpoints gutter. michael@0: */ michael@0: function clearDebugLocation(ctx) { michael@0: let { ed } = ctx; michael@0: let meta = dbginfo.get(ed); michael@0: michael@0: if (meta.debugLocation != null) { michael@0: ed.removeMarker(meta.debugLocation, "breakpoints", "debugLocation"); michael@0: ed.removeLineClass(meta.debugLocation, "debug-line"); michael@0: meta.debugLocation = null; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Starts a new search. michael@0: */ michael@0: function find(ctx, query) { michael@0: clearSearch(ctx.cm); michael@0: doSearch(ctx, false, query); michael@0: } michael@0: michael@0: /** michael@0: * Finds the next item based on the currently saved search. michael@0: */ michael@0: function findNext(ctx, query) { michael@0: doSearch(ctx, false, query); michael@0: } michael@0: michael@0: /** michael@0: * Finds the previous item based on the currently saved search. michael@0: */ michael@0: function findPrev(ctx, query) { michael@0: doSearch(ctx, true, query); michael@0: } michael@0: michael@0: michael@0: // Export functions michael@0: michael@0: [ michael@0: initialize, hasBreakpoint, addBreakpoint, removeBreakpoint, michael@0: getBreakpoints, setDebugLocation, getDebugLocation, michael@0: clearDebugLocation, find, findNext, findPrev michael@0: ].forEach(function (func) { module.exports[func.name] = func; });