diff --git a/common/content/hints.js b/common/content/hints.js index cdece8bc..6951d778 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -9,177 +9,102 @@ /** @scope modules */ /** @instance hints */ -var Hints = Module("hints", { - init: function init() { - const self = this; +var HintSession = Class("HintSession", { + init: function init(mode, opts) { + opts = opts || {}; - this._hintMode = null; - this._submode = ""; // used for extended mode, can be "o", "t", "y", etc. - this._hintString = ""; // the typed string part of the hint is in this string - this._hintNumber = 0; // only the numerical part of the hint - this._usedTabKey = false; // when we used to select an element - this.prevInput = ""; // record previous user input type, "text" || "number" - this._extendedhintCount = null; // for the count argument of Mode#action (extended hint only) + // Hack. + if (!opts.window && modes.main == modes.OUTPUT_MULTILINE) + opts.window = commandline.widgets.multilineOutput.contentWindow; - this._pageHints = []; - this._validHints = []; // store the indices of the "hints" array with valid elements + this.mode = hints.modes[mode]; + dactyl.assert(this.mode); - this._activeTimeout = null; // needed for hinttimeout > 0 + this.activeTimeout = null; // needed for hinttimeout > 0 + this.continue = Boolean(opts.continue); + this.docs = []; + this.hintKeys = events.fromString(options["hintkeys"]).map(events.closure.toString); + this.hintNumber = 0; + this.hintString = opts.filter || ""; + this.pageHints = []; + this.prevInput = ""; + this.usedTabKey = false; + this.validHints = []; // store the indices of the "hints" array with valid elements - // keep track of the documents which we generated the hints for - // this._docs = { doc: document, start: start_index in hints[], end: end_index in hints[] } - this._docs = []; + commandline.input(UTF8(this.mode.prompt) + ": ", null, this); + modes.extended = modes.HINTS; - this._resizeTimer = Timer(100, 500, function () { - if (self._top && (modes.extended & modes.HINTS)) { - self._removeHints(0, true); - self._generate(self._top); - self._showHints(); - } - }); - let appContent = document.getElementById("appcontent"); - if (appContent) - events.addSessionListener(appContent, "scroll", this._resizeTimer.closure.tell, false); + this.top = opts.window || content; + this.top.addEventListener("resize", hints.resizeTimer.closure.tell, true); - const Mode = Hints.Mode; - Mode.defaultValue("tags", function () function () options["hinttags"]); - Mode.prototype.__defineGetter__("xpath", function () - options.get("extendedhinttags").getKey(this.name, this.tags())); + this.generate(); - this._hintModes = {}; - this.addMode(";", "Focus hint", buffer.closure.focusElement); - this.addMode("?", "Show information for hint", function (elem) buffer.showElementInfo(elem)); - this.addMode("s", "Save hint", function (elem) buffer.saveLink(elem, false)); - this.addMode("f", "Focus frame", function (elem) dactyl.focus(elem.ownerDocument.defaultView)); - this.addMode("F", "Focus frame or pseudo-frame", buffer.closure.focusElement, null, isScrollable); - this.addMode("o", "Follow hint", function (elem) buffer.followLink(elem, dactyl.CURRENT_TAB)); - this.addMode("t", "Follow hint in a new tab", function (elem) buffer.followLink(elem, dactyl.NEW_TAB)); - this.addMode("b", "Follow hint in a background tab", function (elem) buffer.followLink(elem, dactyl.NEW_BACKGROUND_TAB)); - this.addMode("w", "Follow hint in a new window", function (elem) buffer.followLink(elem, dactyl.NEW_WINDOW)); - this.addMode("O", "Generate an ‘:open URL’ prompt", function (elem, loc) commandline.open(":", "open " + loc, modes.EX)); - this.addMode("T", "Generate a ‘:tabopen URL’ prompt", function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX)); - this.addMode("W", "Generate a ‘:winopen URL’ prompt", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)); - this.addMode("a", "Add a bookmark", function (elem) bookmarks.addSearchKeyword(elem)); - this.addMode("S", "Add a search keyword", function (elem) bookmarks.addSearchKeyword(elem)); - this.addMode("v", "View hint source", function (elem, loc) buffer.viewSource(loc, false)); - this.addMode("V", "View hint source in external editor", function (elem, loc) buffer.viewSource(loc, true)); - this.addMode("y", "Yank hint location", function (elem, loc) dactyl.clipboardWrite(loc, true)); - this.addMode("Y", "Yank hint description", function (elem) dactyl.clipboardWrite(elem.textContent || "", true)); - this.addMode("c", "Open context menu", function (elem) buffer.openContextMenu(elem)); - this.addMode("i", "Show image", function (elem) dactyl.open(elem.src)); - this.addMode("I", "Show image in a new tab", function (elem) dactyl.open(elem.src, dactyl.NEW_TAB)); + this.show(); - function isScrollable(elem) isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]) || - Buffer.isScrollable(elem, 0, true) || Buffer.isScrollable(elem, 0, false); + if (this.validHints.length == 0) { + dactyl.beep(); + modes.pop(); + } + else if (this.validHints.length == 1 && !this.continue) + this.process(false); + else // Ticket #185 + this.checkUnique(); + }, + + get extended() modes.HINTS, + + checkUnique: function _checkUnique() { + if (this.hintNumber == 0) + return; + dactyl.assert(this.hintNumber <= this.validHints.length); + + // if we write a numeric part like 3, but we have 45 hints, only follow + // the hint after a timeout, as the user might have wanted to follow link 34 + if (this.hintNumber > 0 && this.hintNumber * this.hintKeys.length <= this.validHints.length) { + let timeout = options["hinttimeout"]; + if (timeout > 0) + this.activeTimeout = this.timeout(function () { + this.process(true); + }, timeout); + } + else // we have a unique hint + this.process(true); }, /** * Clear any timeout which might be active after pressing a number */ clearTimeout: function () { - if (this._activeTimeout) - this._activeTimeout.cancel(); - this._activeTimeout = null; + if (this.activeTimeout) + this.activeTimeout.cancel(); + this.activeTimeout = null; }, /** - * Reset hints, so that they can be cleanly used again. + * Returns the hint string for a given number based on the values of + * the 'hintkeys' option. + * + * @param {number} n The number to transform. + * @returns {string} */ - _reset: function _reset(slight) { - if (!slight) { - this.__reset(); - this.prevInput = ""; - this.escNumbers = false; - this._usedTabKey = false; - this._hintNumber = 0; - this._hintString = ""; - statusline.updateInputBuffer(""); - commandline.widgets.command = ""; + getHintString: function getHintString(n) { + let res = [], len = this.hintKeys.length; + do { + res.push(this.hintKeys[n % len]); + n = Math.floor(n / len); } - this._pageHints = []; - this._validHints = []; - this._docs = []; - this.clearTimeout(); - }, - __reset: function __reset() { - if (!this._usedTabKey) - this._hintNumber = 0; - if (this._continue && this._validHints.length <= 1) { - this._hintString = ""; - commandline.widgets.command = this._hintString; - this._showHints(); - } - this._updateStatusline(); + while (n > 0); + return res.reverse().join(""); }, /** - * Display the current status to the user. + * Returns true if the given key string represents a + * pseudo-hint-number. + * + * @param {string} key The key to test. + * @returns {boolean} Whether the key represents a hint number. */ - _updateStatusline: function _updateStatusline() { - statusline.updateInputBuffer((hints.escNumbers ? options["mapleader"] : "") + - (this._hintNumber ? this.getHintString(this._hintNumber) : "")); - }, - - /** - * Get a hint for "input", "textarea" and "select". - * - * Tries to use