From af64937d556cb33eb3bd25ae8199fe430ecfd4ce Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 4 Oct 2010 14:17:13 -0400 Subject: [PATCH] Fix some crufty old mode-change related bugginess. --- common/content/commandline.js | 123 +++++++++++++++------------- common/content/dactyl-overlay.js | 2 +- common/content/editor.js | 22 +++++ common/content/hints.js | 6 +- common/content/modes.js | 134 +++++++++++-------------------- common/content/statusline.js | 39 +++++---- common/modules/base.jsm | 2 +- 7 files changed, 165 insertions(+), 163 deletions(-) diff --git a/common/content/commandline.js b/common/content/commandline.js index cc3a5c61..2458d252 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -224,15 +224,6 @@ const CommandLine = Module("commandline", { ////////////////////// TIMERS ////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - this._statusTimer = Timer(5, 100, function statusTell() { - if (self._completions == null) - return; - if (self._completions.selected == null) - statusline.updateProgress(""); - else - statusline.updateProgress("match " + (self._completions.selected + 1) + " of " + self._completions.items.length); - }); - this._autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) { dactyl.trapErrors(function () { if (!events.feedingKeys && self._completions && options["autocomplete"].length) { @@ -243,6 +234,15 @@ const CommandLine = Module("commandline", { }); }); + this._statusTimer = Timer(5, 100, function statusTell() { + if (self._completions == null) + return; + if (self._completions.selected == null) + statusline.progess = ""; + else + statusline.progress = "match " + (self._completions.selected + 1) + " of " + self._completions.items.length; + }); + // This timer just prevents s from queueing up when the // system is under load (and, thus, giving us several minutes of // the completion list scrolling). Multiple presses are @@ -254,6 +254,8 @@ const CommandLine = Module("commandline", { }); }); + this._timers = [this._autocompleteTimer, this._statusTimer, this._tabTimer]; + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// VARIABLES /////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ @@ -273,8 +275,7 @@ const CommandLine = Module("commandline", { // we need to save the mode which were in before opening the command line // this is then used if we focus the command line again without the "official" // way of calling "open" - this._currentExtendedMode = null; // the extended mode which we last openend the command line for - this._currentCommand = null; + this.currentExtendedMode = null; // the extended mode which we last openend the command line for // save the arguments for the inputMultiline method which are needed in the event handler this._multilineEnd = null; @@ -374,6 +375,10 @@ const CommandLine = Module("commandline", { dactyl.triggerObserver("echoMultiline", str, highlightGroup); + this._startHints = false; + if (!(modes.extended & modes.OUTPUT_MULTILINE)) + modes.push(modes.COMMAND_LINE, modes.OUTPUT_MULTILINE); + // If it's already XML, assume it knows what it's doing. // Otherwise, white space is significant. // The problem elsewhere is that E4X tends to insert new lines @@ -403,9 +408,6 @@ const CommandLine = Module("commandline", { win.focus(); - this._startHints = false; - if (!(modes.extended & modes.OUTPUT_MULTILINE)) - modes.set(modes.COMMAND_LINE, modes.OUTPUT_MULTILINE); commandline.updateMorePrompt(); }, @@ -483,12 +485,26 @@ const CommandLine = Module("commandline", { } }, - hideCompletions: function hideCompletions() { + hideCompletions: function () { for (let nodeSet in values([this.widgets.statusbar, this.widgets.commandbar])) if (nodeSet.commandline._completionList) nodeSet.commandline._completionList.hide(); }, + currentExtendedMode: Modes.boundProperty(), + _keepCommand: Modes.boundProperty(), + + multilineInputVisible: Modes.boundProperty({ + set: function (value) { + this.widgets.multilineInput.collapsed = !value; + } + }), + multilineOutputVisible: Modes.boundProperty({ + set: function (value) { + this.widgets.mowContainer.collapsed = !value; + } + }), + /** * Open the command line. The main mode is set to * COMMAND_LINE, the extended mode to extendedMode. @@ -500,13 +516,14 @@ const CommandLine = Module("commandline", { * @param {number} extendedMode */ open: function open(prompt, cmd, extendedMode) { - // save the current prompts, we need it later if the command widget - // receives focus without calling the this.open() method - this._currentCommand = cmd || ""; - this._currentExtendedMode = extendedMode || null; - this._keepCommand = false; + modes.push(modes.COMMAND_LINE, this.currentExtendedMode, { + leave: function (newMode) { + commandline.leave(newMode); + } + }); - modes.set(modes.COMMAND_LINE, this._currentExtendedMode); + this.currentExtendedMode = extendedMode || null; + this._keepCommand = false; this.widgets.active.commandline.collapsed = false; this.widgets.prompt = prompt; @@ -517,44 +534,27 @@ const CommandLine = Module("commandline", { // open the completion list automatically if wanted if (cmd.length) - commandline.triggerCallback("change", this._currentExtendedMode, cmd); + commandline.triggerCallback("change", this.currentExtendedMode, cmd); }, /** - * Closes the command line. This is ordinarily triggered automatically - * by a mode change. Will not hide the command line immediately if - * called directly after a successful command, otherwise it will. + * Called when leaving a command-line mode. */ - close: function close() { - let mode = this._currentExtendedMode; - this._currentExtendedMode = null; - commandline.triggerCallback("cancel", mode); + leave: function leave() { + commandline.triggerCallback("cancel", this.currentExtendedMode); + this._timers.forEach(function (timer) timer.reset()); if (this._completions) this._completions.previewClear(); if (this._history) this._history.save(); - this.resetCompletions(); // cancels any asynchronous completion still going on, must be before we set completions = null - this._completions = null; - this._history = null; - - statusline.updateProgress(""); // we may have a "match x of y" visible - dactyl.focusContent(false); - - this.widgets.multilineInput.collapsed = true; this.hideCompletions(); if (!this._keepCommand || this._silent || this._quiet) { - this.widgets.mowContainer.collapsed = true; commandline.updateMorePrompt(); this.hide(); } - if (!this.widgets.mowContainer.collapsed) { - modes.set(modes.COMMAND_LINE, modes.OUTPUT_MULTILINE); - commandline.updateMorePrompt(); - } - this._keepCommand = false; }, get command() { @@ -573,6 +573,8 @@ const CommandLine = Module("commandline", { this.widgets.message = null; if (modes.main != modes.COMMAND_LINE) this.widgets.command = null; + if (modes.extended != modes.OUTPUT_MULTILINE) + this.multilineOutputVisible = false; }, /** @@ -689,8 +691,16 @@ const CommandLine = Module("commandline", { cancel: extra.onCancel }; - modes.push(modes.COMMAND_LINE, modes.PROMPT); - this._currentExtendedMode = modes.PROMPT; + modes.push(modes.COMMAND_LINE, modes.PROMPT | extra.extended, { + leave: function (newMode) { + commandline.leave(newMode); + if (extra.leave) + extra.leave(newMode); + }, + restore: function (newMode) { extra.restore && extra.restore(newMode) }, + save: function (newMode) { extra.save && extra.save(newMode) } + }); + this.currentExtendedMode = modes.PROMPT; this.widgets.prompt = !prompt ? null : [extra.promptHighlight || "Question", prompt]; this.widgets.command = extra.default || ""; @@ -728,7 +738,7 @@ const CommandLine = Module("commandline", { this._multilineEnd = "\n" + end + "\n"; this._multilineCallback = callbackFunc; - this.widgets.multilineInput.collapsed = false; + this.multilineInputVisible = true; this.widgets.multilineInput.value = ""; this._autosizeMultilineInputWidget(); @@ -762,22 +772,22 @@ const CommandLine = Module("commandline", { } else if (event.type == "input") { this.resetCompletions(); - commandline.triggerCallback("change", this._currentExtendedMode, command); + commandline.triggerCallback("change", this.currentExtendedMode, command); } else if (event.type == "keypress") { let key = events.toString(event); if (this._completions) this._completions.previewClear(); - if (!this._currentExtendedMode) + if (!this.currentExtendedMode) return; // user pressed to carry out a command // user pressing is handled in the global onEscape // FIXME: should trigger "cancel" event if (events.isAcceptKey(key)) { - let mode = this._currentExtendedMode; // save it here, as modes.pop() resets it this._keepCommand = userContext.hidden_option_command_afterimage; - this._currentExtendedMode = null; // Don't let modes.pop trigger "cancel" + let mode = this.currentExtendedMode; + this.currentExtendedMode = null; // Don't let modes.pop trigger "cancel" modes.pop(); commandline.triggerCallback("submit", mode, command); } @@ -804,7 +814,7 @@ const CommandLine = Module("commandline", { // and blur the command line if there is no text left if (command.length == 0) { - commandline.triggerCallback("cancel", this._currentExtendedMode); + commandline.triggerCallback("cancel", this.currentExtendedMode); modes.pop(); } } @@ -838,14 +848,13 @@ const CommandLine = Module("commandline", { let index = text.indexOf(this._multilineEnd); if (index >= 0) { text = text.substring(1, index); + let callback = this._multilineCallback; modes.pop(); - this.widgets.multilineInput.collapsed = true; - this._multilineCallback.call(this, text); + callback.call(this, text); } } else if (events.isCancelKey(key)) { modes.pop(); - this.widgets.multilineInput.collapsed = true; } } else if (event.type == "blur") { @@ -1117,7 +1126,7 @@ const CommandLine = Module("commandline", { 0); doc.body.style.minWidth = ""; - this.widgets.mowContainer.collapsed = false; + this.multilineOutputVisible = true; }, resetCompletions: function resetCompletions() { @@ -1185,7 +1194,7 @@ const CommandLine = Module("commandline", { */ replace: function (val) { this.input.value = val; - commandline.triggerCallback("change", commandline._currentExtendedMode, val, "history"); + commandline.triggerCallback("change", commandline.currentExtendedMode, val, "history"); }, /** @@ -1299,7 +1308,7 @@ const CommandLine = Module("commandline", { complete: function complete(show, tabPressed) { this.context.reset(); this.context.tabPressed = tabPressed; - commandline.triggerCallback("complete", commandline._currentExtendedMode, this.context); + commandline.triggerCallback("complete", commandline.currentExtendedMode, this.context); this.context.updateAsync = true; this.reset(show, tabPressed); this.wildIndex = 0; diff --git a/common/content/dactyl-overlay.js b/common/content/dactyl-overlay.js index f7e2d3fc..f1b66d4c 100644 --- a/common/content/dactyl-overlay.js +++ b/common/content/dactyl-overlay.js @@ -46,6 +46,7 @@ "modules", "storage", "util", + "modes", "abbreviations", "autocommands", "buffer", @@ -64,7 +65,6 @@ "javascript", "mappings", "marks", - "modes", "options", "statusline", "styles", diff --git a/common/content/editor.js b/common/content/editor.js index 63ec1e89..f7bf6707 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -16,6 +16,28 @@ const Editor = Module("editor", { // this._lastFindChar = null; this._lastFindCharFunc = null; + + // Hack? + dactyl.registerObserver("modeChange", function (oldMode, newMode, stack) { + switch (oldMode[0]) { + case modes.TEXTAREA: + case modes.INSERT: + editor.unselectText(); + break; + + case modes.VISUAL: + if (newMode[0] == modes.CARET) { + try { // clear any selection made; a simple if (selection) does not work + let selection = window.content.getSelection(); + selection.collapseToStart(); + } + catch (e) {} + } + else + editor.unselectText(); + break; + } + }); }, line: function () { diff --git a/common/content/hints.js b/common/content/hints.js index b1d91a98..6713cee2 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -753,7 +753,11 @@ const Hints = Module("hints", { this._hintMode = this._hintModes[minor]; dactyl.assert(this._hintMode); - commandline.input(this._hintMode.prompt + ": ", null, { onChange: this.closure._onInput }); + commandline.input(this._hintMode.prompt + ": ", null, { + extended: modes.HINTS, + leave: function () { hints.hide(); }, + onChange: this.closure._onInput + }); modes.extended = modes.HINTS; this.hintKeys = events.fromString(options["hintkeys"]).map(events.closure.toString); diff --git a/common/content/modes.js b/common/content/modes.js index e5ede1f0..93324e89 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -50,6 +50,17 @@ const Modes = Module("modes", { this.addMode("MENU", true); // a popupmenu is active this.addMode("LINE", true); // linewise visual mode this.addMode("PROMPT", true); + + this.push(this.NORMAL, 0, { + restore: function (prev) { + // disable caret mode when we want to switch to normal mode + if (options.getPref("accessibility.browsewithcaret")) + options.setPref("accessibility.browsewithcaret", false); + + statusline.updateUrl(); + dactyl.focusContent(true); + } + }); }, _getModeMessage: function () { @@ -75,52 +86,6 @@ const Modes = Module("modes", { return macromode; }, - // NOTE: Pay attention that you don't run into endless loops - // Usually you should only indicate to leave a special mode like HINTS - // by calling modes.reset() and adding the stuff which is needed - // for its cleanup here - _handleModeChange: function (oldMode, newMode, oldExtended) { - - switch (oldMode) { - case modes.TEXTAREA: - case modes.INSERT: - editor.unselectText(); - break; - - case modes.VISUAL: - if (newMode == modes.CARET) { - try { // clear any selection made; a simple if (selection) does not work - let selection = window.content.getSelection(); - selection.collapseToStart(); - } - catch (e) {} - } - else - editor.unselectText(); - break; - - case modes.CUSTOM: - plugins.stop(); - break; - - case modes.COMMAND_LINE: - // clean up for HINT mode - if (oldExtended & modes.HINTS) - hints.hide(); - commandline.close(); - break; - } - - if (newMode == modes.NORMAL) { - // disable caret mode when we want to switch to normal mode - if (options.getPref("accessibility.browsewithcaret")) - options.setPref("accessibility.browsewithcaret", false); - - statusline.updateUrl(); - dactyl.focusContent(true); - } - }, - NONE: 0, __iterator__: function () array.iterValues(this.all), @@ -188,21 +153,27 @@ const Modes = Module("modes", { // helper function to set both modes in one go // if silent == true, you also need to take care of the mode handling changes yourself - set: function (mainMode, extendedMode, silent, stack) { + set: function (mainMode, extendedMode, params, stack) { + params = params || {}; if (!stack && mainMode != null) - this._modeStack = []; + this.reset(); + + let push = mainMode != null && !(stack && stack.pop) && + Modes.StackElem(mainMode, extendedMode || this.NONE, params, {}); + if (push && this.topOfStack) { + if (this.topOfStack.params.save) + this.topOfStack.params.save(push); - let push = mainMode != null && !(stack && stack.pop); - if (push && this.topOfStack) for (let [id, { obj, prop }] in Iterator(this.boundProperties)) { if (!obj.get()) delete this.boundProperties(id); else - this.topOfStack[2][id] = { obj: obj.get(), prop: prop, value: obj.get()[prop] }; + this.topOfStack.saved[id] = { obj: obj.get(), prop: prop, value: obj.get()[prop] }; } + } - silent = (silent || this._main == mainMode && this._extended == extendedMode); + let silent = this._main === mainMode && this._extended === extendedMode; // if a this._main mode is set, the this._extended is always cleared let oldMain = this._main, oldExtended = this._extended; @@ -211,50 +182,38 @@ const Modes = Module("modes", { if (typeof mainMode === "number") { this._main = mainMode; if (!extendedMode) - this._extended = modes.NONE; - - if (this._main != oldMain) - this._handleModeChange(oldMain, mainMode, oldExtended); + this._extended = this.NONE; } - if (mainMode != null && !(stack && stack.pop)) - this._modeStack.push([this._main, this._extended, {}]); + + if (push) + this._modeStack.push(push); + dactyl.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack); if (!silent) this.show(); }, - push: function (mainMode, extendedMode, silent) { - this.set(mainMode, extendedMode, silent, { push: this.topOfStack }); + push: function (mainMode, extendedMode, params) { + this.set(mainMode, extendedMode, params, { push: this.topOfStack }); }, - pop: function (silent) { + pop: function () { let a = this._modeStack.pop(); - if (!this.topOfStack) - this.reset(silent); - else { - this.set(this.topOfStack[0], this.topOfStack[1], silent, { pop: a }); - for (let [k, { obj, prop, value }] in Iterator(this.topOfStack[2])) - obj[prop] = value; - } + if (a.params.leave) + a.params.leave(this.topOfStack); + + this.set(this.topOfStack.main, this.topOfStack.extended, this.topOfStack.params, { pop: a }); + if (this.topOfStack.params.restore) + this.topOfStack.params.restore(a); + + for (let [k, { obj, prop, value }] in Iterator(this.topOfStack.saved)) + obj[prop] = value; }, - // TODO: Deprecate this in favor of addMode? --Kris - // Ya --djk - setCustomMode: function (modestr, oneventfunc, stopfunc) { - // TODO this.plugin[id]... ('id' maybe submode or what..) - plugins.mode = modestr; - plugins.onEvent = oneventfunc; - plugins.stop = stopfunc; - }, - - // keeps recording state - reset: function (silent) { - this._modeStack = []; - if (config.isComposeWindow) - this.set(modes.COMPOSE, modes.NONE, silent); - else - this.set(modes.NORMAL, modes.NONE, silent); + reset: function () { + while (this._modeStack.length > 1) + this.pop(); }, remove: function (mode) { @@ -282,8 +241,10 @@ const Modes = Module("modes", { get extended() this._extended, set extended(value) { this.set(null, value); } }, { + StackElem: Struct("main", "extended", "params", "saved"), cacheId: 0, boundProperty: function boundProperty(desc) { + desc = desc || {}; let id = this.cacheId++, value; return Class.Property(update({ enumerable: true, @@ -295,8 +256,9 @@ const Modes = Module("modes", { return val === undefined ? value : val; }, set: function (val) { - value = desc.set.call(this, val); - value = value === undefined ? val : value; + if (desc.set) + value = desc.set.call(this, val); + value = !desc.set || value === undefined ? val : value; modes.save(id, this, prop) } }) diff --git a/common/content/statusline.js b/common/content/statusline.js index 4c96841b..60149aba 100644 --- a/common/content/statusline.js +++ b/common/content/statusline.js @@ -169,26 +169,31 @@ const StatusLine = Module("statusline", { * A number n <= 0 - Displayed as a "Loading" message. * Any other number - The progress is cleared. */ - updateProgress: function updateProgress(progress) { - if (!progress) - progress = ""; + progress: Modes.boundProperty({ + set: function setProgress(progress) { + if (!progress) + progress = ""; - if (typeof progress == "string") - this.widgets.progress.value = progress; - else if (typeof progress == "number") { - let progressStr = ""; - if (progress <= 0) - progressStr = "[ Loading... ]"; - else if (progress < 1) { - progress = Math.floor(progress * 20); - progressStr = "[" - + "====================".substr(0, progress) - + ">" - + " ".substr(0, 19 - progress) - + "]"; + if (typeof progress == "string") + this.widgets.progress.value = progress; + else if (typeof progress == "number") { + let progressStr = ""; + if (progress <= 0) + progressStr = "[ Loading... ]"; + else if (progress < 1) { + progress = Math.floor(progress * 20); + progressStr = "[" + + "====================".substr(0, progress) + + ">" + + " ".substr(0, 19 - progress) + + "]"; + } + this.widgets.progress.value = progressStr; } - this.widgets.progress.value = progressStr; } + }), + updateProgress: function updateProgress(progress) { + this.progress = progress; }, /** diff --git a/common/modules/base.jsm b/common/modules/base.jsm index d45ed4dd..209e5658 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -779,7 +779,7 @@ Class.prototype = { const self = this; let notify = { notify: function notify(timer) { callback.call(self) } }; let timer = services.create("timer"); - timer.initWithCallback(notify, timeout, timer.TYPE_ONE_SHOT); + timer.initWithCallback(notify, timeout || 0, timer.TYPE_ONE_SHOT); return timer; } };