diff --git a/content/bookmarks.js b/content/bookmarks.js index 980cde44..5b638e69 100644 --- a/content/bookmarks.js +++ b/content/bookmarks.js @@ -193,6 +193,11 @@ liberator.Bookmarks = function () //{{{ liberator.storage.removeObserver("bookmark-cache", bookmarkObserver) }); + liberator.registerObserver("enter", function () { + if (liberator.options["preload"]) + cache.bookmarks; // Forces a load, if not already loaded. + }); + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// OPTIONS ///////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ @@ -533,12 +538,9 @@ liberator.History = function () //{{{ return faviconService.getFaviconImageForPage(ioService.newURI(uri, null, null)).spec; } - var history = []; + var history; var cachedHistory = []; // add pages here after loading the initial Places history - if (liberator.options["preload"]) - setTimeout(function () { load(); }, 100); - function load() { history = []; @@ -556,7 +558,7 @@ liberator.History = function () //{{{ for (let i = 0; i < rootNode.childCount; i++) { var node = rootNode.getChild(i); - //liberator.dump("History child " + node.itemId + ": " + node.title + " - " + node.type + "\n"); + // liberator.dump("History child " + node.itemId + ": " + node.title + " - " + node.type); if (node.type == node.RESULT_TYPE_URI) // just make sure it's a bookmark history.push([node.uri, node.title || "[No title]", getIcon(node.uri)]); } @@ -565,6 +567,11 @@ liberator.History = function () //{{{ rootNode.containerOpen = false; } + liberator.registerObserver("enter", function () { + if (liberator.options["preload"]) + load(); + }); + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// MAPPINGS //////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ @@ -710,6 +717,8 @@ liberator.History = function () //{{{ if (!history) load(); + if (!filter) + return cachedHistory.concat(history); return liberator.completion.cached("history", filter, function() cachedHistory.concat(history), "filterURLArray"); }, @@ -721,14 +730,17 @@ liberator.History = function () //{{{ if (!history) load(); - // don' let cachedHistory grow too large + let filter = function (h) h[0] != url; + // don't let cachedHistory grow too large if (cachedHistory.length > 1000) { history = cachedHistory.concat(history); cachedHistory = []; } else - cachedHistory = cachedHistory.filter(function (elem) elem[0] != url); + cachedHistory = cachedHistory.filter(filter); + if (history.some(function (h) h[0] == url)) + history = history.filter(filter); cachedHistory.unshift([url, title || "[No title]", getIcon(url)]); return true; diff --git a/content/buffer.js b/content/buffer.js index cd1c2bef..6f467c1f 100644 --- a/content/buffer.js +++ b/content/buffer.js @@ -35,7 +35,7 @@ liberator.Buffer = function () //{{{ /////////////////////////////////////////////////////////////////////////////{{{ const highlightClasses = ["Boolean", "ErrorMsg", "Filter", "Function", "InfoMsg", "Keyword", - "LineNr", "ModeMsg", "MoreMsg", "Normal", "Null", "Number", "Question", + "LineNr", "ModeMsg", "MoreMsg", "Normal", "Null", "Number", "Object", "Question", "StatusLine", "StatusLineBroken", "StatusLineSecure", "String", "Tag", "Title", "URL", "WarningMsg", ["Hint", ".liberator-hint", "*"], @@ -84,18 +84,18 @@ liberator.Buffer = function () //{{{ { let sheets = system ? systemSheets : userSheets; - if (!force) - { - let errors = checkSyntax(css); - if (errors.length) - return errors.map(function (e) "CSS: " + filter + ": " + e).join("\n"); - } - if (sheets.some(function (s) s[0] == filter && s[1] == css)) return null; filter = filter.split(","); + try + { + this.registerSheet(cssUri(wrapCSS(filter, css)), !force); + } + catch (e) + { + return e.echoerr || e; + } sheets.push([filter, css]); - this.registerSheet(cssUri(wrapCSS(filter, css))); return null; } @@ -136,8 +136,10 @@ liberator.Buffer = function () //{{{ return matches.length; } - this.registerSheet = function (uri) + this.registerSheet = function (uri, doCheckSyntax) { + if (doCheckSyntax) + checkSyntax(uri); uri = ios.newURI(uri, null, null); if (!sss.sheetRegistered(uri, sss.USER_SHEET)) sss.loadAndRegisterSheet(uri, sss.USER_SHEET); @@ -166,9 +168,8 @@ liberator.Buffer = function () //{{{ let queryinterface = XPCOMUtils.generateQI([Components.interfaces.nsIConsoleListener]); /* What happens if more than one thread tries to use this? */ let testDoc = document.implementation.createDocument(XHTML, "doc", null); - function checkSyntax(css) + function checkSyntax(uri) { - let uri = cssUri(namespace + css); let errors = []; let listener = { QueryInterface: queryinterface, @@ -178,7 +179,7 @@ liberator.Buffer = function () //{{{ { message = message.QueryInterface(Components.interfaces.nsIScriptError); if (message.sourceName == uri) - errors.push(message.errorMessage); + errors.push(message); } catch (e) {} } @@ -212,7 +213,14 @@ liberator.Buffer = function () //{{{ { consoleService.unregisterListener(listener); } - return errors; + if (errors.length) + { + let err = new Error("", errors[0].sourceName.replace(/^(data:text\/css,).*/, "$1..."), errors[0].lineNumber); + err.name = "CSSError" + err.message = errors.reduce(function (msg, e) msg + "; " + e.lineNumber + ": " + e.errorMessage, errors.shift().errorMessage); + err.echoerr = err.fileName + ":" + err.lineNumber + ": " + err.message; + throw err; + } } } Styles.prototype = { diff --git a/content/completion.js b/content/completion.js index c9429cae..9849e5e3 100644 --- a/content/completion.js +++ b/content/completion.js @@ -40,8 +40,8 @@ liberator.Completion = function () //{{{ catch (e) {} // the completion substrings, used for showing the longest common match - var cacheFilter = {}; - var cacheResults = {}; + var cacheFilter = {} + var cacheResults = {} var substrings = []; var historyCache = []; var historyResult = null; @@ -84,13 +84,14 @@ liberator.Completion = function () //{{{ { for (let k in obj) { + // Some object members are only accessible as function calls try { yield [k, obj[k]]; continue; } catch (e) {} - yield [k, "inaccessable"] + yield [k, <>inaccessable] } })(); try @@ -124,6 +125,8 @@ liberator.Completion = function () //{{{ if (!(objects instanceof Array)) objects = [objects]; + // See if we can use the cached member list from a previous + // call. if (key.indexOf(lastKey) != 0) continuing = false; if (continuing && objects != lastObjs) @@ -143,33 +146,33 @@ liberator.Completion = function () //{{{ if (!continuing) { + // Can't use the cache. Build a member list. compl = []; for (let [,obj] in Iterator(objects)) { if (typeof obj == "string") obj = eval("with (liberator) {" + obj + "}"); - if (typeof obj != "object") + // Things we can dereference + if (["object", "string", "function"].indexOf(typeof obj) == -1) continue; for (let [k, v] in iter(obj)) - { - let type = typeof v; - if (["string", "number", "boolean"].indexOf(type) > -1) - type += ": " + String(v).replace("\n", "\\n", "g"); - if (type == "function") - type += ": " + String(v).replace(/{(.|\n)*/, "{ ... }"); /* } vim */ - - compl.push([k, type]); - } + compl.push([k, liberator.template.highlight(v, true)]); } - if (last != undefined) + if (last != undefined) // We're looking for a quoted string, so, strip whatever prefix we have and quote the rest compl.forEach(function (a) a[0] = liberator.util.escapeString(a[0].substr(offset), last)); - else + else // We're not looking for a quoted string, so filter out anything that's not a valid identifier compl = compl.filter(function (a) /^[\w$][\w\d$]*$/.test(a[0])); } if (last != undefined) - key = last + key.substr(offset) - return buildLongestStartingSubstring(compl, key); + { + // Filter the results, escaping the key first (without adding quotes), otherwise, it might not match + let res = buildLongestCommonSubstring(compl, liberator.util.escapeString(key.substr(offset), "")); + // Also, prepend the quote delimiter to the substrings list, so it's not stripped on + substrings = substrings.map(function (s) last + s); + return res; + } + return buildLongestCommonSubstring(compl, key); } function eval(arg) @@ -211,6 +214,9 @@ liberator.Completion = function () //{{{ { if (top[CHAR] != arg) throw new Error("Invalid JS"); + // The closing character of this stack frame will have pushed a new + // statement, leaving us with an empty statement. This doesn't matter, + // now, as we simply throw away the frame when we pop it, but it may later. if (top[STATEMENTS][top[STATEMENTS].length - 1] == i) top[STATEMENTS].pop(); top = get(-2); @@ -219,31 +225,28 @@ liberator.Completion = function () //{{{ return ret; } - /* Find the first non-whitespace character fillowing i. */ - let firstNonwhite = function () { - let j = i + 1; - while (str[j] && /\s/.test(str[j])) - j++; - return j; - } - let i = start, c = ""; /* Current index and character, respectively. */ + // We're starting afresh. if (start == 0) { stack = []; - push(""); + push("#root"); } else { + // A new statement may have been pushed onto the stack just after + // the end of the last string. We'll throw it away for now, and + // add it again later if it turns out to be valid. let s = top[STATEMENTS]; if (s[s.length - 1] == start) s.pop(); } - /* Build a parse stack, discarding entries opening characters - * match closing characters. The last open entry is used to - * figure out what to complete. + /* Build a parse stack, discarding entries as opening characters + * match closing characters. The stack is walked from the top entry + * and down as many levels as it takes us to figure out what it is + * that we're completing. */ let length = str.length; for (; i < length; lastChar = c, i++) @@ -251,7 +254,7 @@ liberator.Completion = function () //{{{ c = str[i]; if (last == '"' || last == "'" || last == "/") { - if (lastChar == "\\") + if (lastChar == "\\") // Escape. Skip the next char, whatever it may be. { c = ""; i++; @@ -261,6 +264,8 @@ liberator.Completion = function () //{{{ } else { + // A word character following a non-word character, or simply a non-word + // character. Start a new statement. if (/[\w$]/.test(c) && !/[\w\d$]/.test(lastChar) || !/[\w\d\s]/.test(c)) top[STATEMENTS].push(i); @@ -275,7 +280,10 @@ liberator.Completion = function () //{{{ case "(": /* Function call, or if/while/for/... */ if (/\w/.test(lastNonwhite)) + { top[FUNCTIONS].push(i); + top[STATEMENTS].pop(); + } case '"': case "'": case "/": @@ -325,14 +333,16 @@ liberator.Completion = function () //{{{ /* Okay, have parse stack. Figure out what we're completing. */ - /* Find any complete statements that we can eval. */ + // Find any complete statements that we can eval before we eval our object. + // This allows for things like: let doc = window.content.document; let elem = doc.createElement...; elem. let end = get(0, 0, FULL_STATEMENTS) || 0; let preEval = str.substring(0, end) + ";"; - /* In a string. */ - // TODO: Make this work with unquoted integers. + // In a string. Check if we're dereferencing an object. + // Otherwise, do nothing. if (last == "'" || last == '"') { + // TODO: Make this work with unquoted integers. /* Stack: * [-1]: "... * [-2]: [... @@ -341,7 +351,7 @@ liberator.Completion = function () //{{{ /* Is this an object accessor? */ if (get(-2)[CHAR] != "[" // Are we inside of []? - // Okay, if the [ starts at the begining of a logical + // Yes. If the [ starts at the begining of a logical // statement, we're in an array literal, and we're done. || get(-3, 0, STATEMENTS) == get(-2)[OFFSET]) return [0, []]; @@ -352,16 +362,19 @@ liberator.Completion = function () //{{{ * key = "bar + ''" */ let string = str.substring(top[OFFSET] + 1); - string = eval(last + string + last); + string = eval(last + string + last); // Process escape sequences + // Begining of the statement upto the opening [ let obj = preEval + str.substring(get(-3, 0, STATEMENTS), get(-2)[OFFSET]); + // After the opening [ upto the opening ", plus '' to take care of any operators before it let key = preEval + str.substring(get(-2)[OFFSET] + 1, top[OFFSET]) + "''"; + // Now eval the key, to process any referenced variables. key = eval(key); return [top[OFFSET], objectKeys(obj, key + string, last, key.length)]; } /* Is this an object reference? */ - if (top[DOTS].length) + if (top[DOTS].length && get(-1, 0, DOTS) > get(-1, 0, STATEMENTS)) { let dot = get(-1, 0, DOTS); /* @@ -378,7 +391,7 @@ liberator.Completion = function () //{{{ } /* Okay, assume it's an identifier and try to complete it from the window - * and liberator objects. + * and liberator objects. It would be nice to be able to check the local scope. */ let offset = get(-1, 0, STATEMENTS) || 0; let key = str.substring(offset); @@ -528,13 +541,10 @@ liberator.Completion = function () //{{{ cached: function (key, filter, generate, method) { - let oldFilter = cacheFilter[key]; + if (!filter || filter.indexOf(cacheFilter[key]) != 0) + cacheResults[key] = generate(filter); cacheFilter[key] = filter; - let results = cacheResults[key]; - if (!oldFilter || filter.indexOf(oldFilter) != 0) - results = generate(filter); - cacheResults[key] = this[method].apply(this, [results, filter].concat(Array.splice(arguments, 4))); - return cacheResults[key]; + return cacheResults[key] = this[method].apply(this, [cacheResults[key], filter].concat(Array.splice(arguments, 4))); }, // discard all entries in the 'urls' array, which don't match 'filter @@ -810,11 +820,34 @@ liberator.Completion = function () //{{{ search: function (filter) { - let results = this.cached("search", filter, - function () Array.concat(liberator.bookmarks.getKeywords().map(function (k) [k[0], k[1], k[3]]), - liberator.bookmarks.getSearchEngines()), - "filter", false, true); - return [0, results]; + let [, keyword, args] = filter.match(/^\s*(\S*)\s*(.*)/); + let keywords = liberator.bookmarks.getKeywords().map(function (k) [k[0], k[1], k[3], k[2]]); + let engines = this.filter(keywords.concat(liberator.bookmarks.getSearchEngines()), filter, false, true); + + let history = liberator.history.get(); + let searches = []; + for (let [, k] in Iterator(keywords)) + { + if (k[0].toLowerCase() != keyword.toLowerCase() || k[3].indexOf("%s") == -1) + continue; + let [begin, end] = k[3].split("%s"); + for (let [, h] in Iterator(history)) + { + if (h[0].indexOf(begin) == 0 && (!end.length || h[0].substr(-end.length) == end)) + { + let query = h[0].substr(begin.length, h[0].length - end.length); + searches.push([decodeURIComponent(query), + <>{begin}{query}{end}, + k[2]]); + } + } + } + if (searches.length) + { + searches = this.filter(searches, args, false, true); + searches.forEach(function (a) a[0] = keyword + " " + a[0]); + } + return [0, searches.concat(engines)]; }, // XXX: Move to bookmarks.js? diff --git a/content/events.js b/content/events.js index 8d996d64..c54a482f 100644 --- a/content/events.js +++ b/content/events.js @@ -537,7 +537,7 @@ liberator.Events = function () //{{{ function waitForPageLoaded() { liberator.dump("start waiting in loaded state: " + liberator.buffer.loaded); - liberator.threadyield(true); // clear queue + liberator.threadYield(true); // clear queue if (liberator.buffer.loaded == 1) return true; @@ -546,7 +546,7 @@ liberator.Events = function () //{{{ var then = new Date().getTime(); for (let now = then; now - then < ms; now = new Date().getTime()) { - liberator.threadyield(); + liberator.threadYield(); if ((now - then) % 1000 < 10) liberator.dump("waited: " + (now - then) + " ms"); diff --git a/content/find.js b/content/find.js index 73f28f6c..106f8422 100644 --- a/content/find.js +++ b/content/find.js @@ -204,7 +204,7 @@ liberator.Search = function () //{{{ this.startPt.setStart(node, node.childNodes.length); this.startPt.setEnd(node, node.childNodes.length); if (n++ % 20 == 0) - liberator.threadyield(); + liberator.threadYield(); if (liberator.interrupted) break; } diff --git a/content/hints.js b/content/hints.js index 4a751533..3a46f280 100644 --- a/content/hints.js +++ b/content/hints.js @@ -643,7 +643,7 @@ liberator.Hints = function () //{{{ generate(win); // get all keys from the input queue - liberator.threadyield(true); + liberator.threadYield(true); canUpdate = true; showHints(); diff --git a/content/io.js b/content/io.js index b586961f..e071cc1e 100644 --- a/content/io.js +++ b/content/io.js @@ -763,12 +763,22 @@ lookup: // handle pure javascript files specially if (/\.js$/.test(filename)) { - liberator.eval(str); + var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Components.interfaces.mozIJSSubScriptLoader); + try + { + loader.loadSubScript("file://" + file.path, liberator) + } + catch (e) + { + e.echoerr = file.path + ":" + e.lineNumber + ": " + e; + throw e; + } } else if (/\.css$/.test(filename)) { liberator.storage.styles.unregisterSheet("file://" + file.path); - liberator.storage.styles.registerSheet("file://" + file.path); + liberator.storage.styles.registerSheet("file://" + file.path, !silent); } else { @@ -858,9 +868,9 @@ lookup: } catch (e) { - let message = "Sourcing file: " + file.path + ": " + e; + let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e); if (Components.utils.reportError) - Components.utils.reportError(message); + Components.utils.reportError(e); if (!silent) liberator.echoerr(message); } diff --git a/content/liberator.js b/content/liberator.js index 8eac9852..52cecbe0 100644 --- a/content/liberator.js +++ b/content/liberator.js @@ -1162,6 +1162,7 @@ const liberator = (function () //{{{ option.reset(); } + liberator.triggerObserver("enter", null); liberator.autocommands.trigger(liberator.config.name + "Enter", ""); }, 0); @@ -1193,7 +1194,7 @@ const liberator = (function () //{{{ return true; }, - threadyield: function (flush) + threadYield: function (flush) { let mainThread = threadManager.mainThread; do diff --git a/content/template.js b/content/template.js index b47b6c62..3ded5750 100644 --- a/content/template.js +++ b/content/template.js @@ -8,7 +8,8 @@ liberator.template = { if (fn.length > 1) { iter = Iterator(iter); - fn = function (x) fn.apply(null, x); + let oldfn = fn; + fn = function (x) oldfn.apply(null, x); } else if (iter.length) /* Kludge? */ iter = liberator.util.arrayIter(iter); @@ -56,8 +57,9 @@ liberator.template = { case "boolean": return {arg}; case "function": - return {arg}; - return {String(arg).replace(/\{(.|\n)*/, "{ ... }")}; /* } vim */ + if (processStrings) + return {String(arg).replace(/\{(.|\n)*/, "{ ... }")}; /* } vim */ + return <>{arg}; case "undefined": return {arg}; case "object": @@ -65,7 +67,9 @@ liberator.template = { // that we cannot even try/catch it if (/^\[JavaPackage.*\]$/.test(arg)) return <>[JavaPackage]; - return <>{arg}; + if (processStrings) + arg = String(arg).replace("\n", "\\n", "g"); + return {arg}; default: return ]]>; } @@ -78,9 +82,13 @@ liberator.template = { highlightFilter: function (str, filter) { - let lcstr = str.toLowerCase(); + if (typeof str == "xml") + return str; + if (str == "") + return <>{str}; + let lcstr = String(str).toLowerCase(); let lcfilter = filter.toLowerCase(); - str = str.replace(" ", " "); + str = String(str).replace(" ", " "); let s = <>; let start = 0; let i; diff --git a/content/ui.js b/content/ui.js index 05096637..31c5b4b0 100644 --- a/content/ui.js +++ b/content/ui.js @@ -874,6 +874,8 @@ liberator.CommandLine = function () //{{{ { setCommand(command.substring(0, completionStartIndex) + compl + completionPostfix); commandWidget.selectionStart = commandWidget.selectionEnd = completionStartIndex + compl.length; + if (longest) + liberator.triggerCallback("change", currentExtendedMode, this.getCommand()); // Start a new completion in the next iteration. Useful for commands like :source // RFC: perhaps the command can indicate whether the completion should be restarted @@ -1236,8 +1238,8 @@ liberator.ItemList = function (id) //{{{ let filter = liberator.completion.filterString; if (filter) { - b = liberator.template.highlightFilter(String(b), filter); - c = liberator.template.highlightFilter(String(c), filter); + b = liberator.template.highlightFilter(b, filter); + c = liberator.template.highlightFilter(c, filter); } if (typeof a == "function") diff --git a/content/util.js b/content/util.js index 3233e7fb..fd9415c7 100644 --- a/content/util.js +++ b/content/util.js @@ -119,7 +119,8 @@ liberator.util = { //{{{ escapeString: function (str, delimiter) { - delimiter = delimiter || '"'; + if (delimiter == undefined) + delimiter = '"'; return delimiter + str.replace(/([\\'"])/g, "\\$1").replace("\n", "\\n").replace("\t", "\\t") + delimiter; }, @@ -246,7 +247,7 @@ liberator.util = { //{{{ } if (color) - obj = {obj}.toXMLString(); + obj = {obj}.toXMLString(); string += obj + "::\n"; try // window.content often does not want to be queried with "var i in object" @@ -260,9 +261,10 @@ liberator.util = { //{{{ } catch (e) {} + value = liberator.template.highlight(value, true); if (color) { - value = liberator.template.highlight(value, true).toXMLString(); + value = value.toXMLString(); i = {i}.toXMLString(); } string += i + ": " + value + "\n"; diff --git a/skin/vimperator.css b/skin/vimperator.css index 701ad1ad..3e084707 100644 --- a/skin/vimperator.css +++ b/skin/vimperator.css @@ -95,11 +95,13 @@ the terms of any one of the MPL, the GPL or the LGPL. .hl-String { color: green; } .hl-Boolean { color: blue; } .hl-Null { color: blue; } +.hl-Object { color: maroon; } +.hl-Function{ color: navy; } .hl-Keyword { color: red; } .hl-Tag { color: blue; } -.hl-Filter { font-weight: bold; } +.hl-Filter { font-weight: bold !important; } }