1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 06:07:59 +01:00
Files
pentadactyl-pm/common/content/modes.js
Kris Maglione 6a25312c7d Recfactoring:
* Standard module format. All modules are explicitly declared
   as modules, they're created via a constructor and
   instantiated automatically. They're dependency aware. They
   stringify properly.

 * Classes are declared the same way (rather like Structs
   already were). They also stringify properly. Plus, each
   instance has a rather nifty closure member that closes all
   of its methods around 'this', so you can pass them to map,
   forEach, setTimeout, etc. Modules are themselves classes,
   with a special metaclass, as it were.

 * Doug Crockford is dead, metaphorically speaking.
   Closure-based classes just don't fit into any of the common
   JavaScript frameworks, and they're inefficient and
   confusing. Now, all class and module members are accessed
   explicitly via 'this', which makes it very clear that
   they're class members and not (e.g.) local variables,
   without anything nasty like Hungarian notation.

 * Strictly one module per file. Classes that belong to a
   module live in the same file.

 * For the moment, there are quite a few utility functions
   sitting in base.c, because my class implementation used
   them, and I haven't had the time or inclination to sort them
   out. I plan to reconcile them with the current mess that is
   the util namespace.

 * Changed bracing style.
2009-11-08 20:54:31 -05:00

251 lines
8.3 KiB
JavaScript

// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
/** @scope modules */
const Modes = Module("modes", {
requires: ["util"],
init: function () {
this._main = 1; // NORMAL
this._extended = 0; // NONE
this._lastShown = null;
this._passNextKey = false;
this._passAllKeys = false;
this._isRecording = false;
this._isReplaying = false; // playing a macro
this._modeStack = [];
this._mainModes = [self.NONE];
this._lastMode = 0;
this._modeMap = {};
// main modes, only one should ever be active
this.addMode("NORMAL", { char: "n", display: -1 });
this.addMode("INSERT", { char: "i", input: true });
this.addMode("VISUAL", { char: "v", display: function () "VISUAL" + (this._extended & modes.LINE ? " LINE" : "") });
this.addMode("COMMAND_LINE", { char: "c", input: true });
this.addMode("CARET"); // text cursor is visible
this.addMode("TEXTAREA", { char: "i" });
this.addMode("EMBED", { input: true });
this.addMode("CUSTOM", { display: function () plugins.mode });
// this._extended modes, can include multiple modes, and even main modes
this.addMode("EX", true);
this.addMode("HINTS", true);
this.addMode("INPUT_MULTILINE", true);
this.addMode("OUTPUT_MULTILINE", true);
this.addMode("SEARCH_FORWARD", true);
this.addMode("SEARCH_BACKWARD", true);
this.addMode("SEARCH_VIEW_FORWARD", true);
this.addMode("SEARCH_VIEW_BACKWARD", true);
this.addMode("MENU", true); // a popupmenu is active
this.addMode("LINE", true); // linewise visual mode
this.addMode("PROMPT", true);
config.modes.forEach(function (mode) { this.addMode.apply(this, mode); }, this);
},
_getModeMessage: function () {
if (this._passNextKey && !this._passAllKeys)
return "-- PASS THROUGH (next) --";
else if (this._passAllKeys && !this._passNextKey)
return "-- PASS THROUGH --";
// when recording a macro
let macromode = "";
if (modes.isRecording)
macromode = "recording";
else if (modes.isReplaying)
macromode = "replaying";
let ext = "";
if (this._extended & modes.MENU) // TODO: desirable?
ext += " (menu)";
ext += " --" + macromode;
if (this._main in this._modeMap && typeof this._modeMap[this._main].display == "function")
return "-- " + this._modeMap[this._main].display() + ext;
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();
liberator.focusContent(true);
}
},
NONE: 0,
__iterator__: function () util.Array.itervalues(this.all),
get all() this._mainModes.slice(),
get mainModes() (mode for ([k, mode] in Iterator(modes._modeMap)) if (!mode.extended && mode.name == k)),
get mainMode() this._modeMap[this._main],
addMode: function (name, extended, options) {
let disp = name.replace("_", " ", "g");
this[name] = 1 << this._lastMode++;
if (typeof extended == "object") {
options = extended;
extended = false;
}
this._modeMap[name] = this._modeMap[this[name]] = util.extend({
extended: extended,
count: true,
input: false,
mask: this[name],
name: name,
disp: disp
}, options);
this._modeMap[name].display = this._modeMap[name].display || function () disp;
if (!extended)
this._mainModes.push(this[name]);
if ("mappings" in modules)
mappings.addMode(this[name]);
},
getMode: function (name) this._modeMap[name],
// show the current mode string in the command line
show: function () {
let msg = "";
if (options["showmode"])
msg = this._getModeMessage();
commandline.echo(msg, "ModeMsg", commandline.FORCE_SINGLELINE);
},
// add/remove always work on the this._extended mode only
add: function (mode) {
this._extended |= mode;
this.show();
},
// 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) {
silent = (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;
if (typeof extendedMode === "number")
this._extended = extendedMode;
if (typeof mainMode === "number") {
this._main = mainMode;
if (!extendedMode)
this._extended = modes.NONE;
if (this._main != oldMain)
this._handleModeChange(oldMain, mainMode, oldExtended);
}
liberator.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack);
if (!silent)
this.show();
},
push: function (mainMode, extendedMode, silent) {
this._modeStack.push([this._main, this._extended]);
this.set(mainMode, extendedMode, silent, { push: this._modeStack[this._modeStack.length - 1] });
},
pop: function (silent) {
let a = this._modeStack.pop();
if (a)
this.set(a[0], a[1], silent, { pop: a });
else
this.reset(silent);
},
// 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);
},
remove: function (mode) {
if (this._extended & mode) {
this._extended &= ~mode;
this.show();
}
},
get passNextKey() this._passNextKey,
set passNextKey(value) { this._passNextKey = value; this.show(); },
get passAllKeys() this._passAllKeys,
set passAllKeys(value) { this._passAllKeys = value; this.show(); },
get isRecording() this._isRecording,
set isRecording(value) { this._isRecording = value; this.show(); },
get isReplaying() this._isReplaying,
set isReplaying(value) { this._isReplaying = value; this.show(); },
get main() this._main,
set main(value) { this.set(value); },
get extended() this._extended,
set extended(value) { this.set(null, value); }
});
// vim: set fdm=marker sw=4 ts=4 et: