diff --git a/chrome.manifest b/chrome.manifest index 75423120..f1d7e455 100644 --- a/chrome.manifest +++ b/chrome.manifest @@ -1,5 +1,6 @@ # Firefox -content vimperator content/ -locale vimperator en-US locale/en-US/ +content vimperator content/ +resource vimperator content/ +locale vimperator en-US locale/en-US/ skin vimperator classic/1.0 skin/ overlay chrome://browser/content/browser.xul chrome://vimperator/content/vimperator.xul diff --git a/content/bookmarks.js b/content/bookmarks.js index 969a41df..742bbc86 100644 --- a/content/bookmarks.js +++ b/content/bookmarks.js @@ -790,15 +790,8 @@ liberator.QuickMarks = function () //{{{ ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - var qmarks = {}; - // TODO: move to a storage module - var savedMarks = liberator.options.getPref("extensions.vimperator.quickmarks", "").split("\n"); - - // load the saved quickmarks - for (var i = 0; i < savedMarks.length - 1; i += 2) - { - qmarks[savedMarks[i]] = savedMarks[i + 1]; - } + liberator.storage.newObject("quickmarks", true); + var qmarks = liberator.storage.quickmarks; /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// MAPPINGS //////////////////////////////////////////////// @@ -899,28 +892,28 @@ liberator.QuickMarks = function () //{{{ add: function (qmark, location) { - qmarks[qmark] = location; + qmarks.set(qmark, location); }, remove: function (filter) { var pattern = new RegExp("[" + filter.replace(/\s+/g, "") + "]"); - for (var qmark in qmarks) + for (var [qmark,] in qmarks) { if (pattern.test(qmark)) - delete qmarks[qmark]; + qmarks.remove(qmark); } }, removeAll: function () { - qmarks = {}; + qmarks.clear(); }, jumpTo: function (qmark, where) { - var url = qmarks[qmark]; + var url = qmarks.get(qmark); if (url) liberator.open(url, where); @@ -934,7 +927,7 @@ liberator.QuickMarks = function () //{{{ var uppercaseMarks = []; var numberMarks = []; - for (var qmark in qmarks) + for (var [qmark,] in qmarks) { if (/[a-z]/.test(qmark)) lowercaseMarks.push(qmark); @@ -972,28 +965,13 @@ liberator.QuickMarks = function () //{{{ for (var i = 0; i < marks.length; i++) { list += " " + marks[i] + - "" + liberator.util.escapeHTML(qmarks[marks[i]]) + ""; + "" + liberator.util.escapeHTML(qmarks.get(marks[i])) + ""; } list += ""; liberator.commandline.echo(list, liberator.commandline.HL_NORMAL, liberator.commandline.FORCE_MULTILINE); }, - - destroy: function () - { - // save the quickmarks - var savedQuickMarks = ""; - - for (var i in qmarks) - { - savedQuickMarks += i + "\n"; - savedQuickMarks += qmarks[i] + "\n"; - } - - liberator.options.setPref("extensions.vimperator.quickmarks", savedQuickMarks); - } - }; //}}} }; //}}} diff --git a/content/liberator.js b/content/liberator.js index 4d9f28f1..a6bb08df 100644 --- a/content/liberator.js +++ b/content/liberator.js @@ -1095,12 +1095,11 @@ const liberator = (function () //{{{ { liberator.autocommands.trigger(liberator.config.name + "LeavePre", ""); + liberator.storage.saveAll(); + // save our preferences - liberator.commandline.destroy(); liberator.options.destroy(); liberator.events.destroy(); - if (liberator.has("quickmarks")) - liberator.quickmarks.destroy(); if (liberator.has("bookmarks")) liberator.bookmarks.destroy(); @@ -1167,6 +1166,8 @@ const liberator = (function () //{{{ //}}} })(); //}}} +Components.utils.import("resource://vimperator/storage.jsi", liberator); + // called when the chrome is fully loaded and before the main window is shown window.addEventListener("load", liberator.startup, false); window.addEventListener("unload", liberator.shutdown, false); diff --git a/content/options.js b/content/options.js index cc447f20..af9d07ba 100644 --- a/content/options.js +++ b/content/options.js @@ -36,17 +36,17 @@ liberator.Option = function (names, description, type, defaultValue, extraInfo) if (!extraInfo) extraInfo = {}; - var value = null; - - this.name = names[0]; + let cannonName = names[0]; + this.name = cannonName; this.names = names; this.type = type; - this.scope = (extraInfo.scope & liberator.options.OPTION_SCOPE_BOTH) || liberator.options.OPTION_SCOPE_GLOBAL; // XXX set to BOTH by default someday? - kstep + this.scope = (extraInfo.scope & liberator.options.OPTION_SCOPE_BOTH) || + liberator.options.OPTION_SCOPE_GLOBAL; + // XXX set to BOTH by default someday? - kstep this.description = description || ""; // "", 0 are valid default values this.defaultValue = (defaultValue === undefined) ? null : defaultValue; - value = this.defaultValue; this.setter = extraInfo.setter || null; this.getter = extraInfo.getter || null; @@ -68,6 +68,10 @@ liberator.Option = function (names, description, type, defaultValue, extraInfo) } } + this.__defineGetter__("globalvalue", function() liberator.options.store.get(cannonName)); + this.__defineSetter__("globalvalue", function(val) liberator.options.store.set(cannonName, val)); + this.globalvalue = this.defaultValue; + this.get = function (scope) { if (scope) @@ -85,7 +89,7 @@ liberator.Option = function (names, description, type, defaultValue, extraInfo) if (liberator.has("tabs") && (scope & liberator.options.OPTION_SCOPE_LOCAL)) aValue = liberator.tabs.options[this.name]; if ((scope & liberator.options.OPTION_SCOPE_GLOBAL) && (aValue == undefined)) - aValue = value; + aValue = this.globalvalue; if (this.getter) this.getter.call(this, aValue); @@ -120,7 +124,7 @@ liberator.Option = function (names, description, type, defaultValue, extraInfo) if (liberator.has("tabs") && (scope & liberator.options.OPTION_SCOPE_LOCAL)) liberator.tabs.options[this.name] = newValue; if (scope & liberator.options.OPTION_SCOPE_GLOBAL) - value = newValue; + this.globalvalue = newValue; this.hasChanged = true; }; @@ -131,7 +135,7 @@ liberator.Option = function (names, description, type, defaultValue, extraInfo) this.hasName = function (name) { - return this.names.some(function (e) { return e == name; }); + return this.names.indexOf(name) >= 0; }; this.isValidValue = function (value) @@ -155,6 +159,8 @@ liberator.Options = function () //{{{ ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + liberator.storage.newObject("options", false); + var prefService = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); var options = []; @@ -768,8 +774,6 @@ liberator.Options = function () //{{{ { for (var i = 0; i < options.length; i++) yield options[i]; - - throw StopIteration; }, add: function (names, description, type, defaultValue, extraInfo) @@ -782,9 +786,9 @@ liberator.Options = function () //{{{ if (!option) return false; - for (var i = 0; i < options.length; i++) + for (var opt in Iterator(this)) { - if (options[i].name == option.name) + if (opt.name == option.name) { // never replace for now liberator.log("Warning: '" + names[0] + "' already exists, NOT replacing existing option.", 1); @@ -832,19 +836,19 @@ liberator.Options = function () //{{{ if (!scope) scope = liberator.options.OPTION_SCOPE_BOTH; - for (var i = 0; i < options.length; i++) + for (var opt in Iterator(this)) { - name = options[i].name; - value = options[i].value; - def = options[i].defaultValue; + name = opt.name; + value = opt.value; + def = opt.defaultValue; if (onlyNonDefault && value == def) continue; - if (!(options[i].scope & scope)) + if (!(opt.scope & scope)) continue; - if (options[i].type == "boolean") + if (opt.type == "boolean") { name = value ? " " + name : "no" + name; if (value != def) @@ -882,12 +886,12 @@ liberator.Options = function () //{{{ " Options ---"; var name, value, defaultValue; - for (var i = 0; i < prefArray.length; i++) + prefArray.forEach(function (pref) { - var userValue = prefService.prefHasUserValue(prefArray[i]); - if ((!onlyNonDefault || userValue) && prefArray[i].indexOf(filter) >= 0) + var userValue = prefService.prefHasUserValue(pref); + if ((!onlyNonDefault || userValue) && pref.indexOf(filter) >= 0) { - name = prefArray[i]; + name = pref; value = this.getPref(name); if (typeof value == "string") value = value.substr(0, 100).replace(/\n/g, " "); @@ -907,11 +911,13 @@ liberator.Options = function () //{{{ else list += " " + name + "=" + value + ""; } - } + }); list += ""; liberator.commandline.echo(list, liberator.commandline.HL_NORMAL, liberator.commandline.FORCE_MULTILINE); }, + get store() liberator.storage.options, + getPref: function (name, forcedDefault) { return loadPreference(name, forcedDefault); diff --git a/content/storage.jsi b/content/storage.jsi new file mode 100644 index 00000000..aa7c6795 --- /dev/null +++ b/content/storage.jsi @@ -0,0 +1,236 @@ +/***** BEGIN LICENSE BLOCK ***** {{{ +Version: MPL 1.1/GPL 2.0/LGPL 2.1 + +The contents of this file are subject to the Mozilla Public License Version +1.1 (the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at +http://www.mozilla.org/MPL/ + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +for the specific language governing rights and limitations under the +License. + +(c) 2006-2008: Martin Stubenschrott + +Alternatively, the contents of this file may be used under the terms of +either the GNU General Public License Version 2 or later (the "GPL"), or +the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +in which case the provisions of the GPL or the LGPL are applicable instead +of those above. If you wish to allow use of your version of this file only +under the terms of either the GPL or the LGPL, and not to allow others to +use your version of this file under the terms of the MPL, indicate your +decision by deleting the provisions above and replace them with the notice +and other provisions required by the GPL or the LGPL. If you do not delete +the provisions above, a recipient may use your version of this file under +the terms of any one of the MPL, the GPL or the LGPL. +}}} ***** END LICENSE BLOCK *****/ + +var EXPORTED_SYMBOLS = ["storage"]; + +var prefService = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); +var json = Components.classes["@mozilla.org/dom/json;1"] + .createInstance(Components.interfaces.nsIJSON); + +function getCharPref(name) +{ + try + { + var value = prefService.getComplexValue(name, Components.interfaces.nsISupportsString).data; + // try in case it's a localized string (will throw an exception if not) + if (!prefService.prefIsLocked(name) && !prefService.prefHasUserValue(name) && + /^chrome:\/\/.+\/locale\/.+\.properties/.test(value)) + value = branch.getComplexValue(name, Components.interfaces.nsIPrefLocalizedString).data; + return value; + } + catch (e) + { + return undefined; + } +} + +function prefName(key) { + return "extensions.liberator.datastore." + key; +} + +function loadPref(name, store, type) +{ + if (store) + var pref = getCharPref(prefName(name)); + if (pref) + var obj = json.decode(pref); + if (obj instanceof type) + return obj; +} + +var prototype = { + fireEvent: function(event, arg) { storage.fireEvent(this.name, event, arg) }, + save: function() + { + if(this.store) + prefService.setCharPref(prefName(this.name), this.serial) + }, +}; + +function ObjectStore(name, store) +{ + var object = loadPref(name, store, Object) || {}; + + this.__defineGetter__("store", function() store); + this.__defineGetter__("name", function() name); + this.__defineGetter__("serial", function() json.encode(object)); + + this.set = function set(key, val) + { + var orig = object[key]; + object[key] = val; + if (orig == val) + this.fireEvent("change", key); + else + this.fireEvent("add", key); + }; + + this.remove = function remove(key) + { + var ret = object[key]; + delete object[key]; + this.fireEvent("remove", key); + return ret; + }; + + this.get = function get(val) object[val]; + + this.clear = function() + { + object = {}; + }; + + this.__iterator__ = function() { + for (let key in object) + yield [key, object[key]]; + } +} +ObjectStore.prototype = prototype; + +function ArrayStore(name, store) +{ + var array = loadPref(name, store, Array) || {}; + + this.__defineGetter__("store", function() store); + this.__defineGetter__("name", function() name); + this.__defineGetter__("serial", function() json.encode(array)); + this.__defineGetter__("length", function() array.length); + + this.set = function set(index, value) + { + var orig = array[index]; + array[index] = value; + this.fireEvent("change", index); + }; + + this.push = function push(value) + { + array.push(value); + this.fireEvent("push", array.length); + }; + + this.pop = function pop(value) + { + var ret = array.pop(); + this.fireEvent("pop", array.length); + return ret; + }; + + this.truncate = function truncate(length, fromEnd) + { + var ret = array.length; + if (array.length > length) + { + if(fromEnd) + array.splice(0, array.length - length); + array.length = length; + } + this.fireEvent("truncate", length); + return ret; + }; + + // XXX: Awkward. + this.mutate = function mutate(aFuncName) + { + var funcName = aFuncName; + arguments[0] = array; + array = Array[funcName].apply(Array, arguments); + }, + + this.get = function get(index) + { + return index >= 0 ? array[index] : array[array.length + index]; + }; + + this.__iterator__ = function() { + for(let i = 0; i < array.length; i++) + yield [i, array[i]]; + } +} +ArrayStore.prototype = prototype; + +var keys = {}; +var observers = {}; + +var storage = { + _addKey: function addKey(key, val) + { + if(key in this) + throw Error; // TODO: Find a better error. + keys[key] = val; + this.__defineGetter__(key, function() keys[key]); + }, + + newObject: function newObject(key, store) + { + // TODO: Add type checking. + if(key in keys) + return; + this._addKey(key, new ObjectStore(key, store)); + }, + + newArray: function newArray(key, store) + { + // TODO: Add type checking. + if(key in keys) + return; + this._addKey(key, new ArrayStore(key, store)); + }, + + addObserver: function addObserver(key, callback) + { + if(!(key in observers)) + observers[key] = []; + if(observers[key].indexOf(key) >= 0) + return; + observers[key].push(callback); + }, + + removeObserver: function(key, callback) + { + if(!(key in observers)) + return; + observers[key] = observers[key].filter(function(elem) elem != callback); + if(obsevers[key].length == 0) + delete obsevers[key]; + }, + + fireEvent: function fireEvent(key, event, arg) + { + for each(callback in observers[key]) + callback(key, event, arg); + }, + + saveAll: function storeAll() { + for each(key in keys) + key.store(); + } +}; + +// vim: set fdm=marker sw=4 sts=4 et ft=javascript: diff --git a/content/ui.js b/content/ui.js index 7f1099ca..1255f45d 100644 --- a/content/ui.js +++ b/content/ui.js @@ -43,53 +43,29 @@ liberator.CommandLine = function () //{{{ var completionlist = new liberator.InformationList("liberator-completion", { minItems: 1, maxItems: 10 }); var completions = []; + liberator.storage.newArray("history-search", true); + liberator.storage.newArray("history-command", true); + // TODO: clean this up when it's not 3am... var history = { - get size() { return liberator.options["history"]; }, + get mode() (liberator.modes.extended == liberator.modes.EX) ? "command" : "search", - get mode() { return (liberator.modes.extended == liberator.modes.EX) ? "cmd" : "search"; }, + get store() liberator.storage["history-" + this.mode], - cmd: null, // ex command history + get length() this.store.length, - search: null, // text search history - - get: function () { return this[this.mode]; }, - - set: function (lines) { this[this.mode] = lines; }, - - load: function () - { - // TODO: move to storage module - this.cmd = liberator.options.getPref("extensions.vimperator.commandline_cmd_history", "").split("\n"); - this.search = liberator.options.getPref("extensions.vimperator.commandline_search_history", "").split("\n"); - }, - - save: function () - { - liberator.options.setPref("extensions.vimperator.commandline_cmd_history", this.cmd.join("\n")); - liberator.options.setPref("extensions.vimperator.commandline_search_history", this.search.join("\n")); - }, + get: function(index) this.store.get(index), add: function (str) { if (!str) return; - var lines = this.get(); - - // remove all old history lines which have this string - lines = lines.filter(function (line) { - return line != str; - }); - - // add string to the command line history - if (lines.push(str) > this.size) // remove the first 10% of the history - lines = lines.slice(this.size / 10); - - this.set(lines); + this.store.mutate('filter', function(line) line != str); + this.store.push(str); + this.store.truncate(liberator.options["history"], true); } }; - history.load(); var historyIndex = UNINITIALIZED; var historyStart = ""; @@ -643,8 +619,6 @@ liberator.CommandLine = function () //{{{ // user pressed UP or DOWN arrow to cycle history completion else if (key == "" || key == "") { - var lines = history.get(); - event.preventDefault(); event.stopPropagation(); @@ -654,18 +628,18 @@ liberator.CommandLine = function () //{{{ // save 'start' position for iterating through the history if (historyIndex == UNINITIALIZED) { - historyIndex = lines.length; + historyIndex = history.length; historyStart = command; } // search the history for the first item matching the current // commandline string - while (historyIndex >= -1 && historyIndex <= lines.length) + while (historyIndex >= -1 && historyIndex <= history.length) { key == "" ? historyIndex-- : historyIndex++; // user pressed DOWN when there is no newer history item - if (historyIndex == lines.length) + if (historyIndex == history.length) { setCommand(historyStart); liberator.triggerCallback("change", currentExtendedMode, this.getCommand()); @@ -679,16 +653,16 @@ liberator.CommandLine = function () //{{{ liberator.beep(); break; } - else if (historyIndex >= lines.length + 1) + else if (historyIndex >= history.length + 1) { - historyIndex = lines.length; + historyIndex = history.length; liberator.beep(); break; } - if (lines[historyIndex].indexOf(historyStart) == 0) + if (history.get(historyIndex).indexOf(historyStart) == 0) { - setCommand(lines[historyIndex]); + setCommand(history.get(historyIndex)); liberator.triggerCallback("change", currentExtendedMode, this.getCommand()); break; } @@ -1094,13 +1068,6 @@ liberator.CommandLine = function () //{{{ { completionIndex = historyIndex = UNINITIALIZED; }, - - // it would be better if we had a destructor in javascript ... - destroy: function () - { - history.save(); - } - }; //}}} }; //}}}