diff --git a/common/content/completion.js b/common/content/completion.js index 7734f4d1..fff020c0 100644 --- a/common/content/completion.js +++ b/common/content/completion.js @@ -828,7 +828,7 @@ const Completion = Module("completion", { // v[0] in orig and orig[v[0]] catch different cases. XPCOM // objects are problematic, to say the least. compl = [v for (v in this.iter(obj)) - if ((typeof orig == "object" && v[0] in orig) || getKey(orig, v[0]) !== undefined)]; + if (v && (typeof orig == "object" && v[0] in orig || getKey(orig, v[0]) !== undefined))]; } // And if wrappedJSObject happens to be available, diff --git a/common/content/events.js b/common/content/events.js index f3447103..879e4ed2 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -1063,6 +1063,9 @@ const Events = Module("events", { if (stop) killEvent(); } + catch (e) { + liberator.reportError(e); + } finally { let motionMap = (this._input.pendingMotionMap && this._input.pendingMotionMap.names[0]) || ""; if (!(modes.extended & modes.HINTS)) diff --git a/common/content/finder.js b/common/content/finder.js index 09195e5e..0003699d 100644 --- a/common/content/finder.js +++ b/common/content/finder.js @@ -224,7 +224,7 @@ const Finder = Module("finder", { // TODO: focus the top of the currently visible screen }, - // TODO: backwards seems impossible i fear :( + // TODO: backwards seems impossible i fear /** * Searches the current buffer for str. * @@ -457,4 +457,287 @@ const Finder = Module("finder", { }, }); +const RangeFinder = Module("rangefinder", { + requires: ["config"], + + init: function () { + }, + + openPrompt: function (mode) { + let backwards; + if (mode == modes.FIND_BACKWARD) { + commandline.open("?", "", modes.FIND_BACKWARD); + backwards = true; + } + else { + commandline.open("/", "", modes.FIND_FORWARD); + backwards = false; + } + + this.find("", backwards); + // TODO: focus the top of the currently visible screen + }, + + find: function (str, backwards) { + let caseSensitive = false; + this.rangeFind = RangeFind(caseSensitive, backwards); + + if (!this.rangeFind.search(searchString)) + setTimeout(function () { liberator.echoerr("E486: Pattern not found: " + searchPattern); }, 0); + + return this.rangeFind.found; + }, + + findAgain: function (reverse) { + if (!this.rangeFind || !this.rangeFind.search(null, reverse)) + liberator.echoerr("E486: Pattern not found: " + lastSearchPattern); + else if (this.rangeFind.wrapped) { + // hack needed, because wrapping causes a "scroll" event which clears + // our command line + setTimeout(function () { + if (rangfinder.rangeFind.backward) + commandline.echo("search hit TOP, continuing at BOTTOM", + commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES); + else + commandline.echo("search hit BOTTOM, continuing at TOP", + commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES); + }, 0); + } + else { + liberator.echo((this.rangeFind.backward ? "?" : "/") + lastSearchPattern, null, commandline.FORCE_SINGLELINE); + + if (options["hlsearch"]) + this.highlight(this.rangeFind.lastString); + } + }, + + // Called when the user types a key in the search dialog. Triggers a find attempt if 'incsearch' is set + onKeyPress: function (command) { + if (options["incsearch"] && this.rangeFind) + this.rangeFind.search(command); + }, + + onSubmit: function (command) { + // use the last pattern if none specified + if (!command) + command = lastSearchPattern; + + if (!options["incsearch"] || !this.rangeFind.found) { + this.clear(); + this.find(command, this.rangeFind.backwards); + } + + this._lastSearchBackwards = this.rangeFind.backwards; + this._lastSearchPattern = command; + this._lastSearchString = command; + + if (options["hlsearch"]) + this.highlight(this.rangeFind.searchString); + + modes.reset(); + }, + + // Called when the search is canceled - for example if someone presses + // escape while typing a search + onCancel: function () { + // TODO: code to reposition the document to the place before search started + if (this.rangeFind) + this.rangeFind.cancel(); + this.rangeFind = null; + }, + + get rangeFind() tabs.localStore.rangeFind, + set rangeFind(val) tabs.localStore.rangeFind = val, + + /** + * Highlights all occurances of str in the buffer. + * + * @param {string} str The string to highlight. + */ + highlight: function (str) { + return; + }, + + /** + * Clears all search highlighting. + */ + clear: function () { + return; + } +}, { +}, { + commandline: function () { + // Event handlers for search - closure is needed + commandline.registerCallback("change", modes.FIND_FORWARD, this.closure.onKeyPress); + commandline.registerCallback("submit", modes.FIND_FORWARD, this.closure.onSubmit); + commandline.registerCallback("cancel", modes.FIND_FORWARD, this.closure.onCancel); + // TODO: allow advanced myModes in register/triggerCallback + commandline.registerCallback("change", modes.FIND_BACKWARD, this.closure.onKeyPress); + commandline.registerCallback("submit", modes.FIND_BACKWARD, this.closure.onSubmit); + commandline.registerCallback("cancel", modes.FIND_BACKWARD, this.closure.onCancel); + + }, + commands: function () { + }, + mappings: function () { + var myModes = config.browserModes.concat([modes.CARET]); + + mappings.add(myModes, + ["g/"], "Search forward for a pattern", + function () { rangefinder.openPrompt(modes.FIND_FORWARD); }); + + mappings.add(myModes, + ["g?"], "Search backwards for a pattern", + function () { rangefinder.openPrompt(modes.FIND_BACKWARD); }); + + mappings.add(myModes, + ["g."], "Find next", + function () { rangefinder.findAgain(false); }); + + mappings.add(myModes, + ["g,"], "Find previous", + function () { rangefinder.findAgain(true); }); + + mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["g*"], + "Find word under cursor", + function () { + rangefinder._found = false; + rangefinder.onSubmit(buffer.getCurrentWord(), false); + }); + + mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["g#"], + "Find word under cursor backwards", + function () { + rangefinder._found = false; + rangefinder.onSubmit(buffer.getCurrentWord(), true); + }); + }, + modes: function () { + modes.addMode("FIND_FORWARD", true); + modes.addMode("FIND_BACKWARD", true); + }, + options: function () { + }, +}); + +const RangeFind = Class("RangeFind", { + init: function (matchCase, backward) { + this.finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"] + .createInstance() + .QueryInterface(Components.interfaces.nsIFind); + this.finder.caseSensitive = matchCase; + this.matchCase = matchCase; + this._backward = backward; + this.sel = buffer.selectionController; + this.win = content; + this.doc = content.document; + + this.pageRange = this.doc.createRange(); + this.pageRange.setStartBefore(this.doc.body); + this.pageRange.setEndAfter(this.doc.body); + this.pageStart = this.pageRange.cloneRange(); + this.pageEnd = this.pageRange.cloneRange(); + this.pageStart.collapse(true); + this.pageEnd.collapse(false); + + this.start = Point(this.win.pageXOffset, this.win.pageYOffset); + this.selection = this.sel.getSelection(this.sel.SELECTION_NORMAL); + this.startRange = this.selection.rangeCount ? this.selection.getRangeAt(0) : this.pageStart; + this.startRange.collapse(true); + + this.lastString = ""; + this.lastRange = null; + this.forward = null; + this.found = false; + }, + + // This doesn't work yet. + resetCaret: function () { + let equal = function (r1, r2) !r1.compareToRange(Range.START_TO_START, r2) && !r1.compareToRange(Range.END_TO_END, r2); + letselection = this.win.getSelection(); + if (selection.rangeCount == 0) + selection.addRange(this.pageStart); + function getLines() { + let orig = selection.getRangeAt(0); + function getRanges(forward) { + selection.removeAllRanges(); + selection.addRange(orig); + let cur = orig; + while (true) { + var last = cur; + this.sel.lineMove(forward, false); + cur = selection.getRangeAt(0); + if (equal(cur, last)) + break; + yield cur; + } + } + yield orig; + for (let range in getRanges(true)) + yield range; + for (let range in getRanges(false)) + yield range; + } + for (let range in getLines()) { + if (this.sel.checkVisibility(range.startContainer, range.startOffset, range.startOffset)) + return range; + } + return null; + }, + + get searchString() this.lastString, + get backward() this.finder.findBackwards, + + search: function (word, reverse) { + this.finder.findBackwards = reverse ? !this._backward : this._backward; + let again = word == null; + if (again) + word = this.lastString; + if (!this.matchCase) + word = word.toLowerCase(); + + if (word == "") + var range = this.startRange; + else { + if (this.lastRange) { + if (again) + this.lastRange.collapse(this.backward); + else if (word.indexOf(this.lastString) != 0 || this.backward) + this.lastRange = null; + else + this.lastRange.collapse(true); + } + + var range = this.finder.Find(word, this.pageRange, + this.lastRange || this.startRange, + this.pageEnd); + } + + this.lastString = word; + if (range == null) { + if (this.wrapped) { + this.cancel(); + this.found = false; + return null; + } + this.wrapped = true; + this.lastRange = this.backward ? this.pageEnd : this.pageStart; + return this.search(again ? null : word, reverse); + } + this.wrapped = false; + this.selection.removeAllRanges(); + this.selection.addRange(range); + this.sel.scrollSelectionIntoView(this.sel.SELECTION_NORMAL, 0, false); + this.lastRange = range.cloneRange(); + this.found = true; + return range; + }, + + cancel: function () { + this.selection.removeAllRanges(); + this.selection.addRange(this.startRange); + this.win.scrollTo(this.start.x, this.start.y); + }, +}); + // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/modules.js b/common/content/modules.js index 3bb5ca6c..a068214b 100644 --- a/common/content/modules.js +++ b/common/content/modules.js @@ -23,6 +23,7 @@ window.addEventListener("load", function () { const start = Date.now(); const deferredInit = { load: [] }; const seen = set(); + const loaded = []; function load(module, prereq) { try { @@ -36,23 +37,26 @@ window.addEventListener("load", function () { load(Module.constructors[dep], module.name); dump("Load" + (isstring(prereq) ? " " + prereq + " dependency: " : ": ") + module.name); + loaded.push(module.name); modules[module.name] = module(); function init(mod, module) function () module.INIT[mod].call(modules[module.name], modules[mod]); - for (let [mod, ] in iter(module.INIT)) + for (let mod in values(loaded)) { try { - if (mod in modules) + if (mod in module.INIT) init(mod, module)(); - else { - deferredInit[mod] = deferredInit[mod] || []; - deferredInit[mod].push(init(mod, module)); - } + delete module.INIT[mod] } catch(e) { if (modules.liberator) liberator.reportError(e); } + } + for (let mod in keys(module.INIT)) { + deferredInit[mod] = deferredInit[mod] || []; + deferredInit[mod].push(init(mod, module)); + } for (let [, fn] in iter(deferredInit[module.name] || [])) fn(); }