diff --git a/content/bookmarks.js b/content/bookmarks.js index 16f3bb55..f18ff70b 100644 --- a/content/bookmarks.js +++ b/content/bookmarks.js @@ -32,6 +32,7 @@ vimperator.Bookmarks = function () //{{{ //////////////////////////////////////////////////////////////////////////////// ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + const history_service = Components.classes["@mozilla.org/browser/nav-history-service;1"] .getService(Components.interfaces.nsINavHistoryService); const bookmarks_service = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"] @@ -92,239 +93,242 @@ vimperator.Bookmarks = function () //{{{ // close a container after using it! rootNode.containerOpen = false; } - return; } /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - // if "bypass_cache" is true, it will force a reload of the bookmarks database - // on my PC, it takes about 1ms for each bookmark to load, so loading 1000 bookmarks - // takes about 1 sec - this.get = function (filter, tags, bypass_cache) - { - if (!bookmarks || bypass_cache) - load(); + return { - return vimperator.completion.filterURLArray(bookmarks, filter, tags); - } - - this.add = function (title, url, keyword, tags) - { - if (!bookmarks) - load(); - - // if no protocol specified, default to http://, isn't there a better way? - if (!/^\w+:/.test(url)) - url = "http://" + url; - - try + // if "bypass_cache" is true, it will force a reload of the bookmarks database + // on my PC, it takes about 1ms for each bookmark to load, so loading 1000 bookmarks + // takes about 1 sec + get: function (filter, tags, bypass_cache) { - var uri = io_service.newURI(url, null, null); - var id = bookmarks_service.insertBookmark(bookmarks_service.bookmarksRoot, uri, -1, title); - if (!id) + if (!bookmarks || bypass_cache) + load(); + + return vimperator.completion.filterURLArray(bookmarks, filter, tags); + }, + + add: function (title, url, keyword, tags) + { + if (!bookmarks) + load(); + + // if no protocol specified, default to http://, isn't there a better way? + if (!/^\w+:/.test(url)) + url = "http://" + url; + + try + { + var uri = io_service.newURI(url, null, null); + var id = bookmarks_service.insertBookmark(bookmarks_service.bookmarksRoot, uri, -1, title); + if (!id) + return false; + + if (keyword) + { + bookmarks_service.setKeywordForBookmark(id, keyword); + keywords.unshift([keyword, title, url]); + } + + if (tags) + tagging_service.tagURI(uri, tags); + } + catch (e) + { + vimperator.log(e); return false; - - if (keyword) - { - bookmarks_service.setKeywordForBookmark(id, keyword); - keywords.unshift([keyword, title, url]); } - if (tags) - tagging_service.tagURI(uri, tags); - } - catch (e) + //also update bookmark cache + bookmarks.unshift([url, title, keyword, tags || []]); + return true; + }, + + // returns number of deleted bookmarks + remove: function (url) { - vimperator.log(e); - return false; - } + if (!url) + return 0; - //also update bookmark cache - bookmarks.unshift([url, title, keyword, tags || []]); - return true; - } - - // returns number of deleted bookmarks - this.remove = function (url) - { - if (!url) - return 0; - - var i = 0; - try - { - var uri = io_service.newURI(url, null, null); - var count = {}; - var bmarks = bookmarks_service.getBookmarkIdsForURI(uri, count); - - for (; i < bmarks.length; i++) - bookmarks_service.removeItem(bmarks[i]); - } - catch (e) - { - vimperator.log(e); - return i; - } - - - // also update bookmark cache, if we removed at least one bookmark - if (count.value > 0) - load(); - - return count.value; - } - - // TODO: add filtering - // also ensures that each search engine has a Vimperator-friendly alias - this.getSearchEngines = function () - { - var search_engines = []; - var firefox_engines = search_service.getVisibleEngines({ }); - for (var i in firefox_engines) - { - var alias = firefox_engines[i].alias; - if (!alias || !alias.match(/^[a-z0-9_-]+$/)) - alias = firefox_engines[i].name.replace(/^\W*([a-zA-Z_-]+).*/, "$1").toLowerCase(); - if (!alias) - alias = "search"; // for search engines which we can't find a suitable alias - - // make sure we can use search engines which would have the same alias (add numbers at the end) - var newalias = alias; - for (var j = 1; j <= 10; j++) // <=10 is intentional + var i = 0; + try { - if (!search_engines.some(function (item) { return (item[0] == newalias); })) - break; + var uri = io_service.newURI(url, null, null); + var count = {}; + var bmarks = bookmarks_service.getBookmarkIdsForURI(uri, count); - newalias = alias + j; + for (; i < bmarks.length; i++) + bookmarks_service.removeItem(bmarks[i]); } - // only write when it changed, writes are really slow - if (firefox_engines[i].alias != newalias) - firefox_engines[i].alias = newalias; - - search_engines.push([firefox_engines[i].alias, firefox_engines[i].description]); - } - - return search_engines; - } - - // TODO: add filtering - // format of returned array: - // [keyword, helptext, url] - this.getKeywords = function () - { - if (!keywords) - load(); - - return keywords; - } - - // if @param engine_name is null, it uses the default search engine - // @returns the url for the search string - // if the search also requires a postdata, [url, postdata] is returned - this.getSearchURL = function (text, engine_name) - { - var url = null; - var postdata = null; - if (!engine_name) - engine_name = vimperator.options["defsearch"]; - - // we need to make sure our custom alias have been set, even if the user - // did not :open once before - this.getSearchEngines(); - - // first checks the search engines for a match - var engine = search_service.getEngineByAlias(engine_name); - if (engine) - { - if (text) + catch (e) { - var submission = engine.getSubmission(text, null); - url = submission.uri.spec; - postdata = submission.postData; + vimperator.log(e); + return i; } - else - url = engine.searchForm; - } - else // check for keyword urls + + + // also update bookmark cache, if we removed at least one bookmark + if (count.value > 0) + load(); + + return count.value; + }, + + // TODO: add filtering + // also ensures that each search engine has a Vimperator-friendly alias + getSearchEngines: function () + { + var search_engines = []; + var firefox_engines = search_service.getVisibleEngines({ }); + for (var i in firefox_engines) + { + var alias = firefox_engines[i].alias; + if (!alias || !alias.match(/^[a-z0-9_-]+$/)) + alias = firefox_engines[i].name.replace(/^\W*([a-zA-Z_-]+).*/, "$1").toLowerCase(); + if (!alias) + alias = "search"; // for search engines which we can't find a suitable alias + + // make sure we can use search engines which would have the same alias (add numbers at the end) + var newalias = alias; + for (var j = 1; j <= 10; j++) // <=10 is intentional + { + if (!search_engines.some(function (item) { return (item[0] == newalias); })) + break; + + newalias = alias + j; + } + // only write when it changed, writes are really slow + if (firefox_engines[i].alias != newalias) + firefox_engines[i].alias = newalias; + + search_engines.push([firefox_engines[i].alias, firefox_engines[i].description]); + } + + return search_engines; + }, + + // TODO: add filtering + // format of returned array: + // [keyword, helptext, url] + getKeywords: function () { if (!keywords) load(); - for (var i in keywords) + return keywords; + }, + + // if @param engine_name is null, it uses the default search engine + // @returns the url for the search string + // if the search also requires a postdata, [url, postdata] is returned + getSearchURL: function (text, engine_name) + { + var url = null; + var postdata = null; + if (!engine_name) + engine_name = vimperator.options["defsearch"]; + + // we need to make sure our custom alias have been set, even if the user + // did not :open once before + this.getSearchEngines(); + + // first checks the search engines for a match + var engine = search_service.getEngineByAlias(engine_name); + if (engine) { - if (keywords[i][0] == engine_name) + if (text) { - if (text == null) - text = ""; - url = keywords[i][2].replace(/%s/g, encodeURIComponent(text)); - break; + var submission = engine.getSubmission(text, null); + url = submission.uri.spec; + postdata = submission.postData; } - } - } - - // if we came here, the engine_name is neither a search engine or URL - if (postdata) - return [url, postdata]; - else - return url; // can be null - } - - this.list = function (filter, tags, fullmode) - { - if (fullmode) - { - vimperator.open("chrome://browser/content/bookmarks/bookmarksPanel.xul", vimperator.NEW_TAB); - } - else - { - var items = this.get(filter, tags, false); - - if (items.length == 0) - { - if (filter.length > 0 || tags.length > 0) - vimperator.echoerr("E283: No bookmarks matching \"" + filter + "\""); else - vimperator.echoerr("No bookmarks set"); - - return; + url = engine.searchForm; } - - var title, url, tags, keyword, extra; - var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + - ""; - for (var i = 0; i < items.length; i++) + else // check for keyword urls { - title = vimperator.util.escapeHTML(items[i][1]); - if (title.length > 50) - title = title.substr(0, 47) + "..."; - url = vimperator.util.escapeHTML(items[i][0]); - keyword = items[i][2]; - tags = items[i][3].join(", "); + if (!keywords) + load(); - extra = ""; - if (keyword) + for (var i in keywords) { - extra = " (keyword: " + vimperator.util.escapeHTML(keyword) + ""; - if (tags) - extra += " tags: " + vimperator.util.escapeHTML(tags) + ")"; - else - extra += ")"; + if (keywords[i][0] == engine_name) + { + if (text == null) + text = ""; + url = keywords[i][2].replace(/%s/g, encodeURIComponent(text)); + break; + } } - else if (tags) - { - extra = " (tags: " + vimperator.util.escapeHTML(tags) + ")"; - } - - - list += ""; } - list += "
titleURL
" + title + "" + url + "" + extra + "
"; - vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); + // if we came here, the engine_name is neither a search engine or URL + if (postdata) + return [url, postdata]; + else + return url; // can be null + }, + + list: function (filter, tags, fullmode) + { + if (fullmode) + { + vimperator.open("chrome://browser/content/bookmarks/bookmarksPanel.xul", vimperator.NEW_TAB); + } + else + { + var items = this.get(filter, tags, false); + + if (items.length == 0) + { + if (filter.length > 0 || tags.length > 0) + vimperator.echoerr("E283: No bookmarks matching \"" + filter + "\""); + else + vimperator.echoerr("No bookmarks set"); + + return; + } + + var title, url, tags, keyword, extra; + var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + + ""; + for (var i = 0; i < items.length; i++) + { + title = vimperator.util.escapeHTML(items[i][1]); + if (title.length > 50) + title = title.substr(0, 47) + "..."; + url = vimperator.util.escapeHTML(items[i][0]); + keyword = items[i][2]; + tags = items[i][3].join(", "); + + extra = ""; + if (keyword) + { + extra = " (keyword: " + vimperator.util.escapeHTML(keyword) + ""; + if (tags) + extra += " tags: " + vimperator.util.escapeHTML(tags) + ")"; + else + extra += ")"; + } + else if (tags) + { + extra = " (tags: " + vimperator.util.escapeHTML(tags) + ")"; + } + + + list += ""; + } + list += "
titleURL
" + title + "" + url + "" + extra + "
"; + + vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); + } } - } + + }; //}}} } //}}} @@ -333,6 +337,7 @@ vimperator.History = function () //{{{ //////////////////////////////////////////////////////////////////////////////// ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + const history_service = Components.classes["@mozilla.org/browser/nav-history-service;1"] .getService(Components.interfaces.nsINavHistoryService); @@ -374,106 +379,110 @@ vimperator.History = function () //{{{ ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - this.get = function (filter) - { - if (!history) - load(); + return { - return vimperator.completion.filterURLArray(history, filter); - } - - // the history is automatically added to the Places global history - // so just update our cached history here - this.add = function (url, title) - { - if (!history) - load(); - - history = history.filter(function (elem) { - return elem[0] != url; - }); - - history.unshift([url, title]); - return true; - }; - - // TODO: better names? - // and move to vimperator.buffer.? - this.stepTo = function (steps) - { - var index = getWebNavigation().sessionHistory.index + steps; - - if (index >= 0 && index < getWebNavigation().sessionHistory.count) + get: function (filter) { - getWebNavigation().gotoIndex(index); - } - else + if (!history) + load(); + + return vimperator.completion.filterURLArray(history, filter); + }, + + // the history is automatically added to the Places global history + // so just update our cached history here + add: function (url, title) { - vimperator.beep(); - } - } + if (!history) + load(); - this.goToStart = function () - { - var index = getWebNavigation().sessionHistory.index; + history = history.filter(function (elem) { + return elem[0] != url; + }); - if (index == 0) + history.unshift([url, title]); + return true; + }, + + // TODO: better names? + // and move to vimperator.buffer.? + stepTo: function (steps) { - vimperator.beep(); - return; - } + var index = getWebNavigation().sessionHistory.index + steps; - getWebNavigation().gotoIndex(0); - } - - this.goToEnd = function () - { - var index = getWebNavigation().sessionHistory.index; - var max = getWebNavigation().sessionHistory.count - 1; - - if (index == max) - { - vimperator.beep(); - return; - } - - getWebNavigation().gotoIndex(max); - } - - this.list = function (filter, fullmode) - { - if (fullmode) - { - vimperator.open("chrome://browser/content/history/history-panel.xul", vimperator.NEW_TAB); - } - else - { - var items = this.get(filter); - - if (items.length == 0) + if (index >= 0 && index < getWebNavigation().sessionHistory.count) { - if (filter.length > 0) - vimperator.echoerr("E283: No history matching \"" + filter + "\""); - else - vimperator.echoerr("No history set"); + getWebNavigation().gotoIndex(index); + } + else + { + vimperator.beep(); + } + }, + goToStart: function () + { + var index = getWebNavigation().sessionHistory.index; + + if (index == 0) + { + vimperator.beep(); return; } - var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + - ""; - for (var i = 0; i < items.length; i++) + getWebNavigation().gotoIndex(0); + }, + + goToEnd: function () + { + var index = getWebNavigation().sessionHistory.index; + var max = getWebNavigation().sessionHistory.count - 1; + + if (index == max) { - var title = vimperator.util.escapeHTML(items[i][1]); - if (title.length > 50) - title = title.substr(0, 47) + "..."; - var url = vimperator.util.escapeHTML(items[i][0]); - list += ""; + vimperator.beep(); + return; + } + + getWebNavigation().gotoIndex(max); + }, + + list: function (filter, fullmode) + { + if (fullmode) + { + vimperator.open("chrome://browser/content/history/history-panel.xul", vimperator.NEW_TAB); + } + else + { + var items = this.get(filter); + + if (items.length == 0) + { + if (filter.length > 0) + vimperator.echoerr("E283: No history matching \"" + filter + "\""); + else + vimperator.echoerr("No history set"); + + return; + } + + var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + + "
titleURL
" + title + "" + url + "
"; + for (var i = 0; i < items.length; i++) + { + var title = vimperator.util.escapeHTML(items[i][1]); + if (title.length > 50) + title = title.substr(0, 47) + "..."; + var url = vimperator.util.escapeHTML(items[i][0]); + list += ""; + } + list += "
titleURL
" + title + "" + url + "
"; + vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); } - list += ""; - vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); } - } + + }; //}}} } //}}} @@ -582,153 +591,157 @@ vimperator.Marks = function () //{{{ ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - // TODO: add support for frameset pages - this.add = function (mark) - { - var win = window.content; + return { - if (win.document.body.localName.toLowerCase() == "frameset") - { - vimperator.echoerr("marks support for frameset pages not implemented yet"); - return; - } - - var x = win.scrollMaxX ? win.pageXOffset / win.scrollMaxX : 0; - var y = win.scrollMaxY ? win.pageYOffset / win.scrollMaxY : 0; - var position = { x: x, y: y }; - - if (isURLMark(mark)) - { - vimperator.log("Adding URL mark: " + mark + " | " + win.location.href + " | (" + position.x + ", " + position.y + ") | tab: " + vimperator.tabs.index(vimperator.tabs.getTab()), 5); - url_marks[mark] = { location: win.location.href, position: position, tab: vimperator.tabs.getTab() }; - } - else if (isLocalMark(mark)) - { - // remove any previous mark of the same name for this location - removeLocalMark(mark); - if (!local_marks[mark]) - local_marks[mark] = []; - vimperator.log("Adding local mark: " + mark + " | " + win.location.href + " | (" + position.x + ", " + position.y + ")", 5); - local_marks[mark].push({ location: win.location.href, position: position }); - } - } - - this.remove = function (filter, special) - { - if (special) - { - // :delmarks! only deletes a-z marks - for (var mark in local_marks) - removeLocalMark(mark); - } - else - { - var pattern = new RegExp("[" + filter.replace(/\s+/g, "") + "]"); - for (var mark in url_marks) - { - if (pattern.test(mark)) - removeURLMark(mark); - } - for (var mark in local_marks) - { - if (pattern.test(mark)) - removeLocalMark(mark); - } - } - } - - this.jumpTo = function (mark) - { - var ok = false; - - if (isURLMark(mark)) - { - var slice = url_marks[mark]; - if (slice && slice.tab && slice.tab.linkedBrowser) - { - if (!slice.tab.parentNode) - { - pending_jumps.push(slice); - // NOTE: this obviously won't work on generated pages using - // non-unique URLs, like Vimperator's help :( - vimperator.open(slice.location, vimperator.NEW_TAB); - return; - } - var index = vimperator.tabs.index(slice.tab); - if (index != -1) - { - vimperator.tabs.select(index); - var win = slice.tab.linkedBrowser.contentWindow; - if (win.location.href != slice.location) - { - pending_jumps.push(slice); - win.location.href = slice.location; - return; - } - vimperator.log("Jumping to URL mark: " + mark + " | " + slice.location + " | (" + slice.position.x + ", " + slice.position.y + ") | tab: " + vimperator.tabs.index(slice.tab), 5); - win.scrollTo(slice.position.x * win.scrollMaxX, slice.position.y * win.scrollMaxY); - ok = true; - } - } - } - else if (isLocalMark(mark)) + // TODO: add support for frameset pages + add: function (mark) { var win = window.content; - var slice = local_marks[mark] || []; - for (var i = 0; i < slice.length; i++) + if (win.document.body.localName.toLowerCase() == "frameset") { - if (win.location.href == slice[i].location) - { - vimperator.log("Jumping to local mark: " + mark + " | " + slice[i].location + " | (" + slice[i].position.x + ", " + slice[i].position.y + ")", 5); - win.scrollTo(slice[i].position.x * win.scrollMaxX, slice[i].position.y * win.scrollMaxY); - ok = true; - } - } - } - - if (!ok) - vimperator.echoerr("E20: Mark not set"); // FIXME: move up? - } - - this.list = function (filter) - { - var marks = getSortedMarks(); - - if (marks.length == 0) - { - vimperator.echoerr("No marks set"); - return; - } - - if (filter.length > 0) - { - marks = marks.filter(function (mark) { - if (filter.indexOf(mark[0]) > -1) - return mark; - }); - if (marks.length == 0) - { - vimperator.echoerr("E283: No marks matching \"" + filter + "\""); + vimperator.echoerr("marks support for frameset pages not implemented yet"); return; } - } - var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + - ""; - for (var i = 0; i < marks.length; i++) + var x = win.scrollMaxX ? win.pageXOffset / win.scrollMaxX : 0; + var y = win.scrollMaxY ? win.pageYOffset / win.scrollMaxY : 0; + var position = { x: x, y: y }; + + if (isURLMark(mark)) + { + vimperator.log("Adding URL mark: " + mark + " | " + win.location.href + " | (" + position.x + ", " + position.y + ") | tab: " + vimperator.tabs.index(vimperator.tabs.getTab()), 5); + url_marks[mark] = { location: win.location.href, position: position, tab: vimperator.tabs.getTab() }; + } + else if (isLocalMark(mark)) + { + // remove any previous mark of the same name for this location + removeLocalMark(mark); + if (!local_marks[mark]) + local_marks[mark] = []; + vimperator.log("Adding local mark: " + mark + " | " + win.location.href + " | (" + position.x + ", " + position.y + ")", 5); + local_marks[mark].push({ location: win.location.href, position: position }); + } + }, + + remove: function (filter, special) { - list += "" + - "" + - "" + - "" + - "" + - ""; - } - list += "
marklinecolfile
" + marks[i][0] + "" + Math.round(marks[i][1].position.y * 100) + "%" + Math.round(marks[i][1].position.x * 100) + "%" + vimperator.util.escapeHTML(marks[i][1].location) + "
"; + if (special) + { + // :delmarks! only deletes a-z marks + for (var mark in local_marks) + removeLocalMark(mark); + } + else + { + var pattern = new RegExp("[" + filter.replace(/\s+/g, "") + "]"); + for (var mark in url_marks) + { + if (pattern.test(mark)) + removeURLMark(mark); + } + for (var mark in local_marks) + { + if (pattern.test(mark)) + removeLocalMark(mark); + } + } + }, - vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); - } + jumpTo: function (mark) + { + var ok = false; + + if (isURLMark(mark)) + { + var slice = url_marks[mark]; + if (slice && slice.tab && slice.tab.linkedBrowser) + { + if (!slice.tab.parentNode) + { + pending_jumps.push(slice); + // NOTE: this obviously won't work on generated pages using + // non-unique URLs, like Vimperator's help :( + vimperator.open(slice.location, vimperator.NEW_TAB); + return; + } + var index = vimperator.tabs.index(slice.tab); + if (index != -1) + { + vimperator.tabs.select(index); + var win = slice.tab.linkedBrowser.contentWindow; + if (win.location.href != slice.location) + { + pending_jumps.push(slice); + win.location.href = slice.location; + return; + } + vimperator.log("Jumping to URL mark: " + mark + " | " + slice.location + " | (" + slice.position.x + ", " + slice.position.y + ") | tab: " + vimperator.tabs.index(slice.tab), 5); + win.scrollTo(slice.position.x * win.scrollMaxX, slice.position.y * win.scrollMaxY); + ok = true; + } + } + } + else if (isLocalMark(mark)) + { + var win = window.content; + var slice = local_marks[mark] || []; + + for (var i = 0; i < slice.length; i++) + { + if (win.location.href == slice[i].location) + { + vimperator.log("Jumping to local mark: " + mark + " | " + slice[i].location + " | (" + slice[i].position.x + ", " + slice[i].position.y + ")", 5); + win.scrollTo(slice[i].position.x * win.scrollMaxX, slice[i].position.y * win.scrollMaxY); + ok = true; + } + } + } + + if (!ok) + vimperator.echoerr("E20: Mark not set"); // FIXME: move up? + }, + + list: function (filter) + { + var marks = getSortedMarks(); + + if (marks.length == 0) + { + vimperator.echoerr("No marks set"); + return; + } + + if (filter.length > 0) + { + marks = marks.filter(function (mark) { + if (filter.indexOf(mark[0]) > -1) + return mark; + }); + if (marks.length == 0) + { + vimperator.echoerr("E283: No marks matching \"" + filter + "\""); + return; + } + } + + var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + + ""; + for (var i = 0; i < marks.length; i++) + { + list += "" + + "" + + "" + + "" + + "" + + ""; + } + list += "
marklinecolfile
" + marks[i][0] + "" + Math.round(marks[i][1].position.y * 100) + "%" + Math.round(marks[i][1].position.x * 100) + "%" + vimperator.util.escapeHTML(marks[i][1].location) + "
"; + + vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); + } + + }; //}}} } //}}} @@ -751,90 +764,93 @@ vimperator.QuickMarks = function () //{{{ ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - this.add = function (qmark, location) - { - qmarks[qmark] = location; - } + return { - this.remove = function (filter) - { - var pattern = new RegExp("[" + filter.replace(/\s+/g, "") + "]"); - - for (var qmark in qmarks) + add: function (qmark, location) { - if (pattern.test(qmark)) - delete qmarks[qmark]; - } - } + qmarks[qmark] = location; + }, - this.removeAll = function () - { - qmarks = {}; - } - - this.jumpTo = function (qmark, where) - { - var url = qmarks[qmark]; - - if (url) - vimperator.open(url, where); - else - vimperator.echoerr("E20: QuickMark not set"); - } - - this.list = function (filter) - { - var marks = []; - - // TODO: should we sort these in a-zA-Z0-9 order? - for (var mark in qmarks) - marks.push([mark, qmarks[mark]]); - marks.sort(); - - if (marks.length == 0) + remove: function (filter) { - vimperator.echoerr("No QuickMarks set"); - return; - } + var pattern = new RegExp("[" + filter.replace(/\s+/g, "") + "]"); - if (filter.length > 0) + for (var qmark in qmarks) + { + if (pattern.test(qmark)) + delete qmarks[qmark]; + } + }, + + removeAll: function () { - marks = marks.filter(function (mark) { - if (filter.indexOf(mark[0]) > -1) - return mark; - }); + qmarks = {}; + }, + + jumpTo: function (qmark, where) + { + var url = qmarks[qmark]; + + if (url) + vimperator.open(url, where); + else + vimperator.echoerr("E20: QuickMark not set"); + }, + + list: function (filter) + { + var marks = []; + + // TODO: should we sort these in a-zA-Z0-9 order? + for (var mark in qmarks) + marks.push([mark, qmarks[mark]]); + marks.sort(); + if (marks.length == 0) { - vimperator.echoerr("E283: No QuickMarks matching \"" + filter + "\""); + vimperator.echoerr("No QuickMarks set"); return; } - } - var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + - ""; - for (var i = 0; i < marks.length; i++) + if (filter.length > 0) + { + marks = marks.filter(function (mark) { + if (filter.indexOf(mark[0]) > -1) + return mark; + }); + if (marks.length == 0) + { + vimperator.echoerr("E283: No QuickMarks matching \"" + filter + "\""); + return; + } + } + + var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + + "
QuickMarkURL
"; + for (var i = 0; i < marks.length; i++) + { + list += ""; + } + list += "
QuickMarkURL
" + marks[i][0] + + "" + vimperator.util.escapeHTML(marks[i][1]) + "
"; + + vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); + }, + + destroy: function () { - list += " " + marks[i][0] + - "" + vimperator.util.escapeHTML(marks[i][1]) + ""; + // save the quickmarks + var saved_qmarks = ""; + + for (var i in qmarks) + { + saved_qmarks += i + "\n"; + saved_qmarks += qmarks[i] + "\n"; + } + + vimperator.options.setPref("quickmarks", saved_qmarks); } - list += ""; - - vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); - } - - this.destroy = function () - { - // save the quickmarks - var saved_qmarks = ""; - - for (var i in qmarks) - { - saved_qmarks += i + "\n"; - saved_qmarks += qmarks[i] + "\n"; - } - - vimperator.options.setPref("quickmarks", saved_qmarks); - } + }; //}}} } //}}} diff --git a/content/buffers.js b/content/buffers.js index c00591bd..57250736 100644 --- a/content/buffers.js +++ b/content/buffers.js @@ -31,6 +31,7 @@ vimperator.Buffer = function () //{{{ //////////////////////////////////////////////////////////////////////////////// ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + // used for the "B" mapping to remember the last :buffer[!] command var lastBufferSwitchArgs = ""; var lastBufferSwitchSpecial = true; @@ -126,608 +127,613 @@ vimperator.Buffer = function () //{{{ /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - this.lastInputField = null; // used to keep track of the right field for "gi" + + return { + + lastInputField: null, // used to keep track of the right field for "gi" - this.__defineGetter__("URL", function () - { - // TODO: .URL is not defined for XUL documents - //return window.content.document.URL; - return window.content.document.location.href; - }); - - this.__defineGetter__("pageHeight", function () - { - return window.content.innerHeight; - }); - - this.__defineGetter__("textZoom", function () - { - return getBrowser().mCurrentBrowser.markupDocumentViewer.textZoom * 100; - }); - this.__defineSetter__("textZoom", function (value) - { - setZoom(value, false); - }); - - this.__defineGetter__("fullZoom", function () - { - return getBrowser().mCurrentBrowser.markupDocumentViewer.fullZoom * 100; - }); - this.__defineSetter__("fullZoom", function (value) - { - setZoom(value, true); - }); - - this.__defineGetter__("title", function () - { - return window.content.document.title; - }); - - // returns an XPathResult object - this.evaluateXPath = function (expression, doc, elem, asIterator) - { - if (!doc) - doc = window.content.document; - if (!elem) - elem = doc; - - var result = doc.evaluate(expression, elem, - function lookupNamespaceURI(prefix) { - switch (prefix) { - case "xhtml": - return "http://www.w3.org/1999/xhtml"; - default: - return null; - } - }, - asIterator ? XPathResult.UNORDERED_NODE_ITERATOR_TYPE : XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, - null - ); - - return result; - } - - // in contrast to vim, returns the selection if one is made, - // otherwise tries to guess the current word unter the text cursor - // NOTE: might change the selection - this.getCurrentWord = function () - { - var selection = window.content.getSelection().toString(); - - if (!selection) + get URL() { - var selection_controller = getBrowser().docShell - .QueryInterface(Components.interfaces.nsIInterfaceRequestor) - .getInterface(Components.interfaces.nsISelectionDisplay) - .QueryInterface(Components.interfaces.nsISelectionController); + // TODO: .URL is not defined for XUL documents + //return window.content.document.URL; + return window.content.document.location.href; + }, - selection_controller.setCaretEnabled(true); - selection_controller.wordMove(false, false); - selection_controller.wordMove(true, true); - selection = window.content.getSelection().toString(); - } - - return selection; - } - - this.list = function (fullmode) - { - if (fullmode) + get pageHeight() { - // toggle the special buffer previw window - if (vimperator.bufferwindow.visible()) + return window.content.innerHeight; + }, + + get textZoom() + { + return getBrowser().mCurrentBrowser.markupDocumentViewer.textZoom * 100; + }, + set textZoom(value) + { + setZoom(value, false); + }, + + get fullZoom() + { + return getBrowser().mCurrentBrowser.markupDocumentViewer.fullZoom * 100; + }, + set fullZoom(value) + { + setZoom(value, true); + }, + + get title() + { + return window.content.document.title; + }, + + // returns an XPathResult object + evaluateXPath: function (expression, doc, elem, asIterator) + { + if (!doc) + doc = window.content.document; + if (!elem) + elem = doc; + + var result = doc.evaluate(expression, elem, + function lookupNamespaceURI(prefix) { + switch (prefix) { + case "xhtml": + return "http://www.w3.org/1999/xhtml"; + default: + return null; + } + }, + asIterator ? XPathResult.UNORDERED_NODE_ITERATOR_TYPE : XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, + null + ); + + return result; + }, + + // in contrast to vim, returns the selection if one is made, + // otherwise tries to guess the current word unter the text cursor + // NOTE: might change the selection + getCurrentWord: function () + { + var selection = window.content.getSelection().toString(); + + if (!selection) { - vimperator.bufferwindow.hide(); + var selection_controller = getBrowser().docShell + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsISelectionDisplay) + .QueryInterface(Components.interfaces.nsISelectionController); + + selection_controller.setCaretEnabled(true); + selection_controller.wordMove(false, false); + selection_controller.wordMove(true, true); + selection = window.content.getSelection().toString(); } - else + + return selection; + }, + + list: function (fullmode) + { + if (fullmode) { - var items = vimperator.completion.get_buffer_completions(""); - vimperator.bufferwindow.show(items); - vimperator.bufferwindow.selectItem(getBrowser().mTabContainer.selectedIndex); - } - } - else - { - // TODO: move this to vimperator.buffers.get() - var items = vimperator.completion.get_buffer_completions(""); - var number, indicator, title, url; - - var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + ""; - for (var i = 0; i < items.length; i++) - { - if (i == vimperator.tabs.index()) - indicator = " % "; - else if (i == vimperator.tabs.index(vimperator.tabs.alternate)) - indicator = " # "; - else - indicator = " "; - - [number, title] = items[i][0].split(/:\s+/, 2); - url = items[i][1]; - url = vimperator.util.escapeHTML(url); - title = vimperator.util.escapeHTML(title); - - list += ""; - } - list += "
" + number + "" + indicator + - "" + title + - "" + url + "
"; - - vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); - } - } - - this.scrollBottom = function () - { - scrollToPercentiles(-1, 100); - } - - this.scrollColumns = function (cols) - { - var win = window.document.commandDispatcher.focusedWindow; - const COL_WIDTH = 20; - - if (cols > 0 && win.scrollX >= win.scrollMaxX || cols < 0 && win.scrollX == 0) - vimperator.beep(); - - win.scrollBy(COL_WIDTH * cols, 0); - } - - this.scrollEnd = function () - { - scrollToPercentiles(100, -1); - } - - this.scrollLines = function (lines) - { - var win = window.document.commandDispatcher.focusedWindow; - checkScrollYBounds(win, lines); - win.scrollByLines(lines); - } - - this.scrollPages = function (pages) - { - var win = window.document.commandDispatcher.focusedWindow; - checkScrollYBounds(win, pages); - win.scrollByPages(pages); - } - - this.scrollToPercentile = function (percentage) - { - scrollToPercentiles(-1, percentage); - } - - this.scrollStart = function () - { - scrollToPercentiles(0, -1); - } - - this.scrollTop = function () - { - scrollToPercentiles(-1, 0); - } - - // TODO: allow callback for filtering out unwanted frames? User defined? - this.shiftFrameFocus = function (count, forward) - { - if (!window.content.document instanceof HTMLDocument) - return; - - var frames = []; - - // find all frames - depth-first search - (function (frame) - { - if (frame.document.body.localName.toLowerCase() == "body") - frames.push(frame); - for (var i = 0; i < frame.frames.length; i++) - arguments.callee(frame.frames[i]) - })(window.content); - - if (frames.length == 0) // currently top is always included - return; - - // remove all unfocusable frames - // TODO: find a better way to do this - walking the tree is too slow - var start = document.commandDispatcher.focusedWindow; - frames = frames.filter(function (frame) { - frame.focus(); - if (document.commandDispatcher.focusedWindow == frame) - return frame; - }); - start.focus(); - - // find the currently focused frame index - // TODO: If the window is a frameset then the first _frame_ should be - // focused. Since this is not the current FF behaviour, - // we initalize current to -1 so the first call takes us to the - // first frame. - var current = -1; - for (var i = 0; i < frames.length; i++) - { - if (frames[i] == document.commandDispatcher.focusedWindow) - { - var current = i; - break; - } - } - - // calculate the next frame to focus - var next = current; - if (forward) - { - if (count > 1) - next = current + count; - else - next++; - - if (next > frames.length - 1) - { - if (current == frames.length - 1) - vimperator.beep(); // still allow the frame indicator to be activated - - next = frames.length - 1; - } - } - else - { - if (count > 1) - next = current - count; - else - next--; - - if (next < 0) - { - if (current == 0) - vimperator.beep(); // still allow the frame indicator to be activated - - next = 0; - } - } - - // focus next frame and scroll into view - frames[next].focus(); - if (frames[next] != window.content) - frames[next].frameElement.scrollIntoView(false); - - // add the frame indicator - // TODO: make this an XBL element rather than messing with the content - // document - var doc = frames[next].document; - var indicator = doc.createElement("div"); - indicator.id = "vimperator-frame-indicator"; - // NOTE: need to set a high z-index - it's a crapshoot! - var style = "background-color: red; opacity: 0.5; z-index: 999;" + - "position: fixed; top: 0; bottom: 0; left: 0; right: 0;"; - indicator.setAttribute("style", style); - doc.body.appendChild(indicator); - - // remove the frame indicator - setTimeout(function () { doc.body.removeChild(indicator); }, 500); - } - - // updates the buffer preview in place only if list is visible - this.updateBufferList = function () - { - if (!vimperator.bufferwindow.visible()) - return false; - - var items = vimperator.completion.get_buffer_completions(""); - vimperator.bufferwindow.show(items); - vimperator.bufferwindow.selectItem(getBrowser().mTabContainer.selectedIndex); - } - - // XXX: should this be in v.buffers. or v.tabs.? - // "buffer" is a string which matches the URL or title of a buffer, if it - // is null, the last used string is used again - this.switchTo = function (buffer, allowNonUnique, count, reverse) - { - if (buffer != null) - { - // store this command, so it can be repeated with "B" - lastBufferSwitchArgs = buffer; - lastBufferSwitchSpecial = allowNonUnique; - } - else - { - buffer = lastBufferSwitchArgs; - if (typeof allowNonUnique == "undefined" || allowNonUnique == null) - allowNonUnique = lastBufferSwitchSpecial; - } - - if (!count || count < 1) - count = 1; - if (typeof reverse != "boolean") - reverse = false; - - var match; - if (match = buffer.match(/^(\d+):?/)) - return vimperator.tabs.select(parseInt(match[1], 10) - 1, false); // make it zero-based - - var matches = []; - var lower_buffer = buffer.toLowerCase(); - var first = vimperator.tabs.index() + (reverse ? 0 : 1); - for (var i = 0; i < getBrowser().browsers.length; i++) - { - var index = (i + first) % getBrowser().browsers.length; - var url = getBrowser().getBrowserAtIndex(index).contentDocument.location.href; - var title = getBrowser().getBrowserAtIndex(index).contentDocument.title.toLowerCase(); - if (url == buffer) - return vimperator.tabs.select(index, false); - - if (url.indexOf(buffer) >= 0 || title.indexOf(lower_buffer) >= 0) - matches.push(index); - } - if (matches.length == 0) - vimperator.echoerr("E94: No matching buffer for " + buffer); - else if (matches.length > 1 && !allowNonUnique) - vimperator.echoerr("E93: More than one match for " + buffer); - else - { - if (reverse) - { - index = matches.length - count; - while (index < 0) - index += matches.length; - } - else - index = (count - 1) % matches.length; - - vimperator.tabs.select(matches[index], false); - } - }; - - this.zoomIn = function (steps, full_zoom) - { - bumpZoomLevel(steps, full_zoom); - }; - - this.zoomOut = function (steps, full_zoom) - { - bumpZoomLevel(-steps, full_zoom); - }; - - this.pageInfo = function (verbose) - { - // TODO: copied from firefox. Needs some review/work... - // const feedTypes = { - // "application/rss+xml": gBundle.getString("feedRss"), - // "application/atom+xml": gBundle.getString("feedAtom"), - // "text/xml": gBundle.getString("feedXML"), - // "application/xml": gBundle.getString("feedXML"), - // "application/rdf+xml": gBundle.getString("feedXML") - // }; - function isValidFeed(aData, aPrincipal, aIsFeed) - { - if (!aData || !aPrincipal) - return false; - - if (!aIsFeed) - { - var type = aData.type && aData.type.toLowerCase(); - type = type.replace(/^\s+|\s*(?:;.*)?$/g, ""); - - aIsFeed = (type == "application/rss+xml" || type == "application/atom+xml"); - if (!aIsFeed) + // toggle the special buffer previw window + if (vimperator.bufferwindow.visible()) { - // really slimy: general XML types with magic letters in the title - const titleRegex = /(^|\s)rss($|\s)/i; - aIsFeed = ((type == "text/xml" || type == "application/rdf+xml" || - type == "application/xml") && titleRegex.test(aData.title)); + vimperator.bufferwindow.hide(); + } + else + { + var items = vimperator.completion.get_buffer_completions(""); + vimperator.bufferwindow.show(items); + vimperator.bufferwindow.selectItem(getBrowser().mTabContainer.selectedIndex); + } + } + else + { + // TODO: move this to vimperator.buffers.get() + var items = vimperator.completion.get_buffer_completions(""); + var number, indicator, title, url; + + var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + ""; + for (var i = 0; i < items.length; i++) + { + if (i == vimperator.tabs.index()) + indicator = " % "; + else if (i == vimperator.tabs.index(vimperator.tabs.alternate)) + indicator = " # "; + else + indicator = " "; + + [number, title] = items[i][0].split(/:\s+/, 2); + url = items[i][1]; + url = vimperator.util.escapeHTML(url); + title = vimperator.util.escapeHTML(title); + + list += ""; + } + list += "
" + number + "" + indicator + + "" + title + + "" + url + "
"; + + vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); + } + }, + + scrollBottom: function () + { + scrollToPercentiles(-1, 100); + }, + + scrollColumns: function (cols) + { + var win = window.document.commandDispatcher.focusedWindow; + const COL_WIDTH = 20; + + if (cols > 0 && win.scrollX >= win.scrollMaxX || cols < 0 && win.scrollX == 0) + vimperator.beep(); + + win.scrollBy(COL_WIDTH * cols, 0); + }, + + scrollEnd: function () + { + scrollToPercentiles(100, -1); + }, + + scrollLines: function (lines) + { + var win = window.document.commandDispatcher.focusedWindow; + checkScrollYBounds(win, lines); + win.scrollByLines(lines); + }, + + scrollPages: function (pages) + { + var win = window.document.commandDispatcher.focusedWindow; + checkScrollYBounds(win, pages); + win.scrollByPages(pages); + }, + + scrollToPercentile: function (percentage) + { + scrollToPercentiles(-1, percentage); + }, + + scrollStart: function () + { + scrollToPercentiles(0, -1); + }, + + scrollTop: function () + { + scrollToPercentiles(-1, 0); + }, + + // TODO: allow callback for filtering out unwanted frames? User defined? + shiftFrameFocus: function (count, forward) + { + if (!window.content.document instanceof HTMLDocument) + return; + + var frames = []; + + // find all frames - depth-first search + (function (frame) + { + if (frame.document.body.localName.toLowerCase() == "body") + frames.push(frame); + for (var i = 0; i < frame.frames.length; i++) + arguments.callee(frame.frames[i]) + })(window.content); + + if (frames.length == 0) // currently top is always included + return; + + // remove all unfocusable frames + // TODO: find a better way to do this - walking the tree is too slow + var start = document.commandDispatcher.focusedWindow; + frames = frames.filter(function (frame) { + frame.focus(); + if (document.commandDispatcher.focusedWindow == frame) + return frame; + }); + start.focus(); + + // find the currently focused frame index + // TODO: If the window is a frameset then the first _frame_ should be + // focused. Since this is not the current FF behaviour, + // we initalize current to -1 so the first call takes us to the + // first frame. + var current = -1; + for (var i = 0; i < frames.length; i++) + { + if (frames[i] == document.commandDispatcher.focusedWindow) + { + var current = i; + break; } } - if (aIsFeed) + // calculate the next frame to focus + var next = current; + if (forward) + { + if (count > 1) + next = current + count; + else + next++; + + if (next > frames.length - 1) + { + if (current == frames.length - 1) + vimperator.beep(); // still allow the frame indicator to be activated + + next = frames.length - 1; + } + } + else + { + if (count > 1) + next = current - count; + else + next--; + + if (next < 0) + { + if (current == 0) + vimperator.beep(); // still allow the frame indicator to be activated + + next = 0; + } + } + + // focus next frame and scroll into view + frames[next].focus(); + if (frames[next] != window.content) + frames[next].frameElement.scrollIntoView(false); + + // add the frame indicator + // TODO: make this an XBL element rather than messing with the content + // document + var doc = frames[next].document; + var indicator = doc.createElement("div"); + indicator.id = "vimperator-frame-indicator"; + // NOTE: need to set a high z-index - it's a crapshoot! + var style = "background-color: red; opacity: 0.5; z-index: 999;" + + "position: fixed; top: 0; bottom: 0; left: 0; right: 0;"; + indicator.setAttribute("style", style); + doc.body.appendChild(indicator); + + // remove the frame indicator + setTimeout(function () { doc.body.removeChild(indicator); }, 500); + }, + + // updates the buffer preview in place only if list is visible + updateBufferList: function () + { + if (!vimperator.bufferwindow.visible()) + return false; + + var items = vimperator.completion.get_buffer_completions(""); + vimperator.bufferwindow.show(items); + vimperator.bufferwindow.selectItem(getBrowser().mTabContainer.selectedIndex); + }, + + // XXX: should this be in v.buffers. or v.tabs.? + // "buffer" is a string which matches the URL or title of a buffer, if it + // is null, the last used string is used again + switchTo: function (buffer, allowNonUnique, count, reverse) + { + if (buffer != null) + { + // store this command, so it can be repeated with "B" + lastBufferSwitchArgs = buffer; + lastBufferSwitchSpecial = allowNonUnique; + } + else + { + buffer = lastBufferSwitchArgs; + if (typeof allowNonUnique == "undefined" || allowNonUnique == null) + allowNonUnique = lastBufferSwitchSpecial; + } + + if (!count || count < 1) + count = 1; + if (typeof reverse != "boolean") + reverse = false; + + var match; + if (match = buffer.match(/^(\d+):?/)) + return vimperator.tabs.select(parseInt(match[1], 10) - 1, false); // make it zero-based + + var matches = []; + var lower_buffer = buffer.toLowerCase(); + var first = vimperator.tabs.index() + (reverse ? 0 : 1); + for (var i = 0; i < getBrowser().browsers.length; i++) + { + var index = (i + first) % getBrowser().browsers.length; + var url = getBrowser().getBrowserAtIndex(index).contentDocument.location.href; + var title = getBrowser().getBrowserAtIndex(index).contentDocument.title.toLowerCase(); + if (url == buffer) + return vimperator.tabs.select(index, false); + + if (url.indexOf(buffer) >= 0 || title.indexOf(lower_buffer) >= 0) + matches.push(index); + } + if (matches.length == 0) + vimperator.echoerr("E94: No matching buffer for " + buffer); + else if (matches.length > 1 && !allowNonUnique) + vimperator.echoerr("E93: More than one match for " + buffer); + else + { + if (reverse) + { + index = matches.length - count; + while (index < 0) + index += matches.length; + } + else + index = (count - 1) % matches.length; + + vimperator.tabs.select(matches[index], false); + } + }, + + zoomIn: function (steps, full_zoom) + { + bumpZoomLevel(steps, full_zoom); + }, + + zoomOut: function (steps, full_zoom) + { + bumpZoomLevel(-steps, full_zoom); + }, + + pageInfo: function (verbose) + { + // TODO: copied from firefox. Needs some review/work... + // const feedTypes = { + // "application/rss+xml": gBundle.getString("feedRss"), + // "application/atom+xml": gBundle.getString("feedAtom"), + // "text/xml": gBundle.getString("feedXML"), + // "application/xml": gBundle.getString("feedXML"), + // "application/rdf+xml": gBundle.getString("feedXML") + // }; + function isValidFeed(aData, aPrincipal, aIsFeed) + { + if (!aData || !aPrincipal) + return false; + + if (!aIsFeed) + { + var type = aData.type && aData.type.toLowerCase(); + type = type.replace(/^\s+|\s*(?:;.*)?$/g, ""); + + aIsFeed = (type == "application/rss+xml" || type == "application/atom+xml"); + if (!aIsFeed) + { + // really slimy: general XML types with magic letters in the title + const titleRegex = /(^|\s)rss($|\s)/i; + aIsFeed = ((type == "text/xml" || type == "application/rdf+xml" || + type == "application/xml") && titleRegex.test(aData.title)); + } + } + + if (aIsFeed) + { + try + { + urlSecurityCheck(aData.href, aPrincipal, + Components.interfaces.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL); + } + catch(ex) + { + aIsFeed = false; + } + } + + if (type) + aData.type = type; + + return aIsFeed; + } + + // TODO: could this be useful for other commands? + function createTable(data) + { + var ret = ""; + + if (data.length - 1) + { + for (var i = 0; i < data.length - 1 ; i++) + ret += ""; + } + else + { + ret += ""; + } + + return ret + "
" + + data[data.length -1][0] + "
" + data[i][0] + ": " + data[i][1] + "
(" + data[data.length - 1][1] + ")
"; + } + + var pageGeneral = []; + var pageFeeds = []; + var pageMeta = []; + + // get file size + const nsICacheService = Components.interfaces.nsICacheService; + const ACCESS_READ = Components.interfaces.nsICache.ACCESS_READ; + const cacheService = Components.classes["@mozilla.org/network/cache-service;1"].getService(nsICacheService); + var httpCacheSession = cacheService.createSession("HTTP", 0, true); + var ftpCacheSession = cacheService.createSession("FTP", 0, true); + httpCacheSession.doomEntriesIfExpired = false; + ftpCacheSession.doomEntriesIfExpired = false; + var cacheKey = window.content.document.location.toString().replace(/#.*$/, ""); + try + { + var cacheEntryDescriptor = httpCacheSession.openCacheEntry(cacheKey, ACCESS_READ, false); + } + catch (ex) { try { - urlSecurityCheck(aData.href, aPrincipal, - Components.interfaces.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL); - } - catch(ex) - { - aIsFeed = false; + cacheEntryDescriptor = ftpCacheSession.openCacheEntry(cacheKey, ACCESS_READ, false); } + catch (ex2) { } } - if (type) - aData.type = type; - - return aIsFeed; - } - - // TODO: could this be useful for other commands? - function createTable(data) - { - var ret = ""; - - if (data.length - 1) + var pageSize = []; // [0] bytes; [1] kbytes + if (cacheEntryDescriptor) { - for (var i = 0; i < data.length - 1 ; i++) - ret += ""; - } - else - { - ret += ""; + pageSize[0] = vimperator.util.formatNumber(cacheEntryDescriptor.dataSize); + pageSize[1] = vimperator.util.formatNumber(Math.round(cacheEntryDescriptor.dataSize / 1024 * 100) / 100); } - return ret + "
" + - data[data.length -1][0] + "
" + data[i][0] + ": " + data[i][1] + "
(" + data[data.length - 1][1] + ")
"; - } - - var pageGeneral = []; - var pageFeeds = []; - var pageMeta = []; - - // get file size - const nsICacheService = Components.interfaces.nsICacheService; - const ACCESS_READ = Components.interfaces.nsICache.ACCESS_READ; - const cacheService = Components.classes["@mozilla.org/network/cache-service;1"].getService(nsICacheService); - var httpCacheSession = cacheService.createSession("HTTP", 0, true); - var ftpCacheSession = cacheService.createSession("FTP", 0, true); - httpCacheSession.doomEntriesIfExpired = false; - ftpCacheSession.doomEntriesIfExpired = false; - var cacheKey = window.content.document.location.toString().replace(/#.*$/, ""); - try - { - var cacheEntryDescriptor = httpCacheSession.openCacheEntry(cacheKey, ACCESS_READ, false); - } - catch (ex) - { - try - { - cacheEntryDescriptor = ftpCacheSession.openCacheEntry(cacheKey, ACCESS_READ, false); - } - catch (ex2) { } - } - - var pageSize = []; // [0] bytes; [1] kbytes - if (cacheEntryDescriptor) - { - pageSize[0] = vimperator.util.formatNumber(cacheEntryDescriptor.dataSize); - pageSize[1] = vimperator.util.formatNumber(Math.round(cacheEntryDescriptor.dataSize / 1024 * 100) / 100); - } - - // put feeds rss into pageFeeds[] - var linkNodes = window.content.document.getElementsByTagName("link"); - var length = linkNodes.length; - for (var i = 0; i < length; i++) - { - var link = linkNodes[i]; - if (!link.href) - continue; - - var rel = link.rel && link.rel.toLowerCase(); - var rels = {}; - if (rel) - { - for each (let relVal in rel.split(/\s+/)) - rels[relVal] = true; - } - - if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) - { - var feed = { title: link.title, href: link.href, type: link.type || "" }; - if (isValidFeed(feed, window.content.document.nodePrincipal, rels.feed)) - { -// var type = feedTypes[feed.type] || feedTypes["application/rss+xml"]; // TODO: dig into that.. --calmar - var type = feed.type || "application/rss+xml"; - pageFeeds.push([feed.title, vimperator.util.highlightURL(feed.href, true)]); - } - } - } - - var lastMod = new Date(window.content.document.lastModified).toLocaleString(); - // FIXME: probably unportable across differnet language versions - if (lastMod == "Invalid Date") - lastMod = null; - - - // Ctrl-g single line output - if (!verbose) - { - var info = []; // tmp array for joining later - var file = window.content.document.location.pathname.split("/").pop() || "[No Name]"; - var title = window.content.document.title || "[No Title]"; - - if (pageSize[1]) - info.push(pageSize[1] + "KiB"); - - if (lastMod) - info.push(lastMod); - - var countFeeds = ""; - if (pageFeeds.length) - countFeeds = pageFeeds.length + (pageFeeds.length == 1 ? " feed" : " feeds"); - - if (countFeeds) - info.push(countFeeds); - - var pageInfoText = '"' + file + '" [' + info.join(", ") + "] " + title; - vimperator.echo(pageInfoText, vimperator.commandline.FORCE_SINGLELINE); - return; - } - - // get general infos - pageGeneral.push(["Title", window.content.document.title]); - pageGeneral.push(["URL", vimperator.util.highlightURL(window.content.document.location.toString(), true)]); - - var ref = "referrer" in window.content.document && window.content.document.referrer; - if (ref) - pageGeneral.push(["Referrer", vimperator.util.highlightURL(ref, true)]); - - if (pageSize[0]) - pageGeneral.push(["File Size", pageSize[1] + "KiB (" + pageSize[0] + " bytes)"]); - - pageGeneral.push(["Mime-Type", content.document.contentType]); - pageGeneral.push(["Encoding", content.document.characterSet]); - pageGeneral.push(["Compatibility", content.document.compatMode == "BackCompat" ? "Quirks Mode" : "Full/Almost Standards Mode"]); - if (lastMod) - pageGeneral.push(["Last Modified", lastMod]); - - // get meta tag data, sort and put into pageMeta[] - var metaNodes = window.content.document.getElementsByTagName("meta"); - var length = metaNodes.length; - if (length) - { - var tmpSort = []; - var tmpDict = []; - + // put feeds rss into pageFeeds[] + var linkNodes = window.content.document.getElementsByTagName("link"); + var length = linkNodes.length; for (var i = 0; i < length; i++) { - var tmpTag = metaNodes[i].name || metaNodes[i].httpEquiv;// + - var tmpTagNr = tmpTag + "-" + i; // allows multiple (identical) meta names - tmpDict[tmpTagNr] = [tmpTag, metaNodes[i].content]; - tmpSort.push(tmpTagNr); // array for sorting + var link = linkNodes[i]; + if (!link.href) + continue; + + var rel = link.rel && link.rel.toLowerCase(); + var rels = {}; + if (rel) + { + for each (let relVal in rel.split(/\s+/)) + rels[relVal] = true; + } + + if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) + { + var feed = { title: link.title, href: link.href, type: link.type || "" }; + if (isValidFeed(feed, window.content.document.nodePrincipal, rels.feed)) + { + // var type = feedTypes[feed.type] || feedTypes["application/rss+xml"]; // TODO: dig into that.. --calmar + var type = feed.type || "application/rss+xml"; + pageFeeds.push([feed.title, vimperator.util.highlightURL(feed.href, true)]); + } + } } - // sort: ignore-case - tmpSort.sort(function (a,b){return a.toLowerCase() > b.toLowerCase() ? 1 : -1;}); - for (var i=0; i < tmpSort.length; i++) - pageMeta.push([tmpDict[tmpSort[i]][0], vimperator.util.highlightURL(tmpDict[tmpSort[i]][1], false)]); - } + var lastMod = new Date(window.content.document.lastModified).toLocaleString(); + // FIXME: probably unportable across differnet language versions + if (lastMod == "Invalid Date") + lastMod = null; - pageMeta.push(["Meta Tags", ""]); // add extra text to the end - pageGeneral.push(["General Info", ""]); - pageFeeds.push(["Feeds", ""]); - var pageInfoText = ""; - var option = vimperator.options["pageinfo"]; - var br = ""; - - for (var z = 0; z < option.length; z++) - { - switch (option[z]) + // Ctrl-g single line output + if (!verbose) { - case "g": - if (pageGeneral.length > 1) - { - pageInfoText += br + createTable(pageGeneral); - if (!br) - br = "
"; - } - break; - case "f": - if (pageFeeds.length > 1) - { - pageInfoText += br + createTable(pageFeeds); - if (!br) - br = "
"; - } - break; - case "m": - if (pageMeta.length > 1) - { - pageInfoText += br + createTable(pageMeta); - if (!br) - br = "
"; - } - break; + var info = []; // tmp array for joining later + var file = window.content.document.location.pathname.split("/").pop() || "[No Name]"; + var title = window.content.document.title || "[No Title]"; + + if (pageSize[1]) + info.push(pageSize[1] + "KiB"); + + if (lastMod) + info.push(lastMod); + + var countFeeds = ""; + if (pageFeeds.length) + countFeeds = pageFeeds.length + (pageFeeds.length == 1 ? " feed" : " feeds"); + + if (countFeeds) + info.push(countFeeds); + + var pageInfoText = '"' + file + '" [' + info.join(", ") + "] " + title; + vimperator.echo(pageInfoText, vimperator.commandline.FORCE_SINGLELINE); + return; } + + // get general infos + pageGeneral.push(["Title", window.content.document.title]); + pageGeneral.push(["URL", vimperator.util.highlightURL(window.content.document.location.toString(), true)]); + + var ref = "referrer" in window.content.document && window.content.document.referrer; + if (ref) + pageGeneral.push(["Referrer", vimperator.util.highlightURL(ref, true)]); + + if (pageSize[0]) + pageGeneral.push(["File Size", pageSize[1] + "KiB (" + pageSize[0] + " bytes)"]); + + pageGeneral.push(["Mime-Type", content.document.contentType]); + pageGeneral.push(["Encoding", content.document.characterSet]); + pageGeneral.push(["Compatibility", content.document.compatMode == "BackCompat" ? "Quirks Mode" : "Full/Almost Standards Mode"]); + if (lastMod) + pageGeneral.push(["Last Modified", lastMod]); + + // get meta tag data, sort and put into pageMeta[] + var metaNodes = window.content.document.getElementsByTagName("meta"); + var length = metaNodes.length; + if (length) + { + var tmpSort = []; + var tmpDict = []; + + for (var i = 0; i < length; i++) + { + var tmpTag = metaNodes[i].name || metaNodes[i].httpEquiv;// + + var tmpTagNr = tmpTag + "-" + i; // allows multiple (identical) meta names + tmpDict[tmpTagNr] = [tmpTag, metaNodes[i].content]; + tmpSort.push(tmpTagNr); // array for sorting + } + + // sort: ignore-case + tmpSort.sort(function (a,b){return a.toLowerCase() > b.toLowerCase() ? 1 : -1;}); + for (var i=0; i < tmpSort.length; i++) + pageMeta.push([tmpDict[tmpSort[i]][0], vimperator.util.highlightURL(tmpDict[tmpSort[i]][1], false)]); + } + + pageMeta.push(["Meta Tags", ""]); // add extra text to the end + pageGeneral.push(["General Info", ""]); + pageFeeds.push(["Feeds", ""]); + + var pageInfoText = ""; + var option = vimperator.options["pageinfo"]; + var br = ""; + + for (var z = 0; z < option.length; z++) + { + switch (option[z]) + { + case "g": + if (pageGeneral.length > 1) + { + pageInfoText += br + createTable(pageGeneral); + if (!br) + br = "
"; + } + break; + case "f": + if (pageFeeds.length > 1) + { + pageInfoText += br + createTable(pageFeeds); + if (!br) + br = "
"; + } + break; + case "m": + if (pageMeta.length > 1) + { + pageInfoText += br + createTable(pageMeta); + if (!br) + br = "
"; + } + break; + } + } + vimperator.echo(pageInfoText, vimperator.commandline.FORCE_MULTILINE); } - vimperator.echo(pageInfoText, vimperator.commandline.FORCE_MULTILINE); - } + + }; //}}} } //}}} diff --git a/content/completion.js b/content/completion.js index 8eeeca25..44213467 100644 --- a/content/completion.js +++ b/content/completion.js @@ -610,7 +610,7 @@ vimperator.Completion = function () // {{{ return [start, completions]; } //}}} - } + }; } // }}} // vim: set fdm=marker sw=4 ts=4 et: diff --git a/content/editor.js b/content/editor.js index 0008e506..3db8f5ba 100644 --- a/content/editor.js +++ b/content/editor.js @@ -50,593 +50,599 @@ vimperator.Editor = function () //{{{ return ed.controllers.getControllerForCommand("cmd_beginLine"); } - this.line = function () - { - var line = 1; - var text = editor().value; - for (var i = 0; i < editor().selectionStart; i++) - if (text[i] == "\n") - line++; - return line; - } + return { - this.col = function () - { - var col = 1; - var text = editor().value; - for (var i = 0; i < editor().selectionStart; i++) + line: function () { - col++; - if (text[i] == "\n") - col = 1; - } - return col; - } + var line = 1; + var text = editor().value; + for (var i = 0; i < editor().selectionStart; i++) + if (text[i] == "\n") + line++; + return line; + }, - this.unselectText = function () - { - var elt = window.document.commandDispatcher.focusedElement; - if (elt && elt.selectionEnd) - elt.selectionEnd = elt.selectionStart; - } - - this.selectedText = function () - { - var text = editor().value; - return text.substring(editor().selectionStart, editor().selectionEnd); - } - - this.pasteClipboard = function () - { - var elt = window.document.commandDispatcher.focusedElement; - - if (elt.setSelectionRange && readFromClipboard()) - // readFromClipboard would return 'undefined' if not checked - // dunno about .setSelectionRange + col: function () { - var rangeStart = elt.selectionStart; // caret position - var rangeEnd = elt.selectionEnd; - var tempStr1 = elt.value.substring(0,rangeStart); - var tempStr2 = readFromClipboard(); - var tempStr3 = elt.value.substring(rangeEnd); - elt.value = tempStr1 + tempStr2 + tempStr3; - elt.selectionStart = rangeStart + tempStr2.length; - elt.selectionEnd = elt.selectionStart; - } - } - - // count is optional, defaults to 1 - this.executeCommand = function (cmd, count) - { - var controller = getController(); - if (!controller || !controller.supportsCommand(cmd) || !controller.isCommandEnabled(cmd)) - { - vimperator.beep(); - return false; - } - - if (typeof count != "number" || count < 1) - count = 1; - - var did_command = false; - while (count--) - { - // some commands need this try/catch workaround, because a cmd_charPrevious triggered - // at the beginning of the textarea, would hang the doCommand() - // good thing is, we need this code anyway for proper beeping - try + var col = 1; + var text = editor().value; + for (var i = 0; i < editor().selectionStart; i++) { - controller.doCommand(cmd); - did_command = true; + col++; + if (text[i] == "\n") + col = 1; } - catch (e) + return col; + }, + + unselectText: function () + { + var elt = window.document.commandDispatcher.focusedElement; + if (elt && elt.selectionEnd) + elt.selectionEnd = elt.selectionStart; + }, + + selectedText: function () + { + var text = editor().value; + return text.substring(editor().selectionStart, editor().selectionEnd); + }, + + pasteClipboard: function () + { + var elt = window.document.commandDispatcher.focusedElement; + + if (elt.setSelectionRange && readFromClipboard()) + // readFromClipboard would return 'undefined' if not checked + // dunno about .setSelectionRange { - if (!did_command) - vimperator.beep(); - return false; + var rangeStart = elt.selectionStart; // caret position + var rangeEnd = elt.selectionEnd; + var tempStr1 = elt.value.substring(0,rangeStart); + var tempStr2 = readFromClipboard(); + var tempStr3 = elt.value.substring(rangeEnd); + elt.value = tempStr1 + tempStr2 + tempStr3; + elt.selectionStart = rangeStart + tempStr2.length; + elt.selectionEnd = elt.selectionStart; } - } + }, - return true; - } - - // cmd = y, d, c - // motion = b, 0, gg, G, etc. - this.executeCommandWithMotion = function (cmd, motion, count) - { - if (!typeof count == "number" || count < 1) - count = 1; - - if (cmd == motion) + // count is optional, defaults to 1 + executeCommand: function (cmd, count) { - motion = "j"; - count--; - } - - vimperator.modes.set(vimperator.modes.VISUAL, vimperator.modes.TEXTAREA); - - switch (motion) - { - case "j": - this.executeCommand("cmd_beginLine", 1); - this.executeCommand("cmd_selectLineNext", count+1); - break; - case "k": - this.executeCommand("cmd_beginLine", 1); - this.executeCommand("cmd_lineNext", 1); - this.executeCommand("cmd_selectLinePrevious", count+1); - break; - case "h": - this.executeCommand("cmd_selectCharPrevious", count); - break; - case "l": - this.executeCommand("cmd_selectCharNext", count); - break; - case "e": - case "w": - this.executeCommand("cmd_selectWordNext", count); - break; - case "b": - this.executeCommand("cmd_selectWordPrevious", count); - break; - case "0": - case "^": - this.executeCommand("cmd_selectBeginLine", 1); - break; - case "$": - this.executeCommand("cmd_selectEndLine", 1); - break; - case "gg": - this.executeCommand("cmd_endLine", 1); - this.executeCommand("cmd_selectTop", 1); - this.executeCommand("cmd_selectBeginLine", 1); - break; - case "G": - this.executeCommand("cmd_beginLine", 1); - this.executeCommand("cmd_selectBottom", 1); - this.executeCommand("cmd_selectEndLine", 1); - break; - - default: + var controller = getController(); + if (!controller || !controller.supportsCommand(cmd) || !controller.isCommandEnabled(cmd)) + { vimperator.beep(); return false; - } - - switch (cmd) - { - case "d": - this.executeCommand("cmd_delete", 1); - // need to reset the mode as the visual selection changes it - vimperator.modes.main = vimperator.modes.TEXTAREA; - break; - case "c": - this.executeCommand("cmd_delete", 1); - vimperator.modes.set(vimperator.modes.INSERT, vimperator.modes.TEXTAREA); - break; - case "y": - this.executeCommand("cmd_copy", 1); - this.unselectText(); - break; - - default: - vimperator.beep(); - return false; - } - return true; - } - - // This function will move/select up to given "pos" - // Simple setSelectionRange() would be better, but we want to maintain the correct - // order of selectionStart/End (a firefox bug always makes selectionStart <= selectionEnd) - // Use only for small movements! - this.moveToPosition = function (pos, forward, select) - { - if (!select) - { - editor().setSelectionRange(pos, pos); - return; - } - - if (forward) - { - if (pos <= editor().selectionEnd || pos > editor().value.length) - return false; - - do // TODO: test code for endless loops - { - this.executeCommand("cmd_selectCharNext", 1); } - while ( editor().selectionEnd != pos ); - } - else - { - if (pos >= editor().selectionStart || pos < 0) - return false; - do // TODO: test code for endless loops + if (typeof count != "number" || count < 1) + count = 1; + + var did_command = false; + while (count--) { - this.executeCommand("cmd_selectCharPrevious", 1); - } - while ( editor().selectionStart != pos ); - } - } - - // returns the position of char - this.findCharForward = function (char, count) - { - if (!editor()) - return -1; - - last_findChar = char; - last_findChar_func = this.findCharForward; - - var text = editor().value; - if (!typeof count == "number" || count < 1) - count = 1; - - for (var i = editor().selectionEnd + 1; i < text.length; i++) - { - if (text[i] == "\n") - break; - if (text[i] == char) - count--; - if (count == 0) - return i + 1; // always position the cursor after the char - } - - vimperator.beep(); - return -1; - } - // returns the position of char - this.findCharBackward = function (char, count) - { - if (!editor()) - return -1; - - last_findChar = char; - last_findChar_func = this.findCharBackward; - - var text = editor().value; - if (!typeof count == "number" || count < 1) - count = 1; - - for (var i = editor().selectionStart - 1; i >= 0; i--) - { - if (text[i] == "\n") - break; - if (text[i] == char) - count--; - if (count == 0) - return i; - } - - vimperator.beep(); - return -1; - } - - this.editWithExternalEditor = function () - { - var textBox = document.commandDispatcher.focusedElement; - var editor = vimperator.options["editor"]; - var args = []; - args = editor.split(" "); - if (args.length < 1) - { - vimperator.echoerr("no editor specified"); - return; - } - - try - { - var tmpfile = vimperator.io.createTempFile(); - } - catch (e) - { - vimperator.echoerr("Could not create temporary file: " + e.message); - return; - } - try - { - vimperator.io.writeFile(tmpfile, textBox.value); - } - catch (e) - { - vimperator.echoerr("Could not write to temporary file " + tmpfile.path + ": " + e.message); - return; - } - - var prog = args.shift(); - args.push(tmpfile.path) - - textBox.setAttribute("readonly", "true"); - var oldBg = textBox.style.backgroundColor; - var tmpBg = "yellow"; - textBox.style.backgroundColor = "#bbbbbb"; - var newThread = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0); - // TODO: save return value in v:shell_error - vimperator.callFunctionInThread(newThread, vimperator.run, [prog, args, true]); - textBox.removeAttribute("readonly"); - - -// if (v:shell_error != 0) -// { -// tmpBg = "red"; -// vimperator.echoerr("External editor returned with exit code " + retcode); -// } -// else - { - try - { - var val = vimperator.io.readFile(tmpfile); - textBox.value = val; - } - catch (e) - { - tmpBg = "red"; - vimperator.echoerr("Could not read from temporary file " + tmpfile.path + ": " + e.message); - } - } - - // blink the textbox after returning - var timeout = 100; - textBox.style.backgroundColor = tmpBg; - setTimeout( function () { - textBox.style.backgroundColor = oldBg; - setTimeout( function () { - textBox.style.backgroundColor = tmpBg; - setTimeout( function () { - textBox.style.backgroundColor = oldBg; - }, timeout); - }, timeout); - }, timeout); - - tmpfile.remove(false); - } - - // Abbreviations {{{ - - this.abbreviations = {}; - this.abbreviations.__iterator__ = function () - { - var tmpCmd; - for (var lhs in abbrev) - { - for (var i = 0; i < abbrev[lhs].length; i++) - { - tmpCmd = (abbrev[lhs][i][0] == "!") ? "abbreviate" : abbrev[lhs][i][0] + "abbrev"; - yield (tmpCmd + " " + lhs + " " + abbrev[lhs][i][1] + "\n"); - } - } - } - - // filter is i, c or "!" (insert or command abbreviations or both) - this.listAbbreviations = function (filter, lhs) - { - if (lhs) // list only that one - { - if (abbrev[lhs]) - { - for (var i = 0; i < abbrev[lhs].length; i++) + // some commands need this try/catch workaround, because a cmd_charPrevious triggered + // at the beginning of the textarea, would hang the doCommand() + // good thing is, we need this code anyway for proper beeping + try { - if (abbrev[lhs][i][0] == filter) - vimperator.echo(abbrev[lhs][i][0] + " " + lhs + " " + abbrev[lhs][i][1]); - return true; + controller.doCommand(cmd); + did_command = true; + } + catch (e) + { + if (!did_command) + vimperator.beep(); + return false; } } - vimperator.echoerr("No abbreviations found"); - return false; - } - else // list all (for that filter {i,c,!}) - { - var flagFound = false; - var searchFilter = (filter == "!") ? "!ci" : filter + "!"; // ! -> list all, on c or i ! matches too) - var list = ""; - for (var tmplhs in abbrev) - { - for (var i = 0; i < abbrev[tmplhs].length; i++) - { - if (searchFilter.indexOf(abbrev[tmplhs][i][0]) > -1) - { - if (!flagFound) - flagFound = true; - list += ""; - list += ""; - list += ""; - list += ""; - list += ""; + return true; + }, + + // cmd = y, d, c + // motion = b, 0, gg, G, etc. + executeCommandWithMotion: function (cmd, motion, count) + { + if (!typeof count == "number" || count < 1) + count = 1; + + if (cmd == motion) + { + motion = "j"; + count--; + } + + vimperator.modes.set(vimperator.modes.VISUAL, vimperator.modes.TEXTAREA); + + switch (motion) + { + case "j": + this.executeCommand("cmd_beginLine", 1); + this.executeCommand("cmd_selectLineNext", count+1); + break; + case "k": + this.executeCommand("cmd_beginLine", 1); + this.executeCommand("cmd_lineNext", 1); + this.executeCommand("cmd_selectLinePrevious", count+1); + break; + case "h": + this.executeCommand("cmd_selectCharPrevious", count); + break; + case "l": + this.executeCommand("cmd_selectCharNext", count); + break; + case "e": + case "w": + this.executeCommand("cmd_selectWordNext", count); + break; + case "b": + this.executeCommand("cmd_selectWordPrevious", count); + break; + case "0": + case "^": + this.executeCommand("cmd_selectBeginLine", 1); + break; + case "$": + this.executeCommand("cmd_selectEndLine", 1); + break; + case "gg": + this.executeCommand("cmd_endLine", 1); + this.executeCommand("cmd_selectTop", 1); + this.executeCommand("cmd_selectBeginLine", 1); + break; + case "G": + this.executeCommand("cmd_beginLine", 1); + this.executeCommand("cmd_selectBottom", 1); + this.executeCommand("cmd_selectEndLine", 1); + break; + + default: + vimperator.beep(); + return false; + } + + switch (cmd) + { + case "d": + this.executeCommand("cmd_delete", 1); + // need to reset the mode as the visual selection changes it + vimperator.modes.main = vimperator.modes.TEXTAREA; + break; + case "c": + this.executeCommand("cmd_delete", 1); + vimperator.modes.set(vimperator.modes.INSERT, vimperator.modes.TEXTAREA); + break; + case "y": + this.executeCommand("cmd_copy", 1); + this.unselectText(); + break; + + default: + vimperator.beep(); + return false; + } + return true; + }, + + // This function will move/select up to given "pos" + // Simple setSelectionRange() would be better, but we want to maintain the correct + // order of selectionStart/End (a firefox bug always makes selectionStart <= selectionEnd) + // Use only for small movements! + moveToPosition: function (pos, forward, select) + { + if (!select) + { + editor().setSelectionRange(pos, pos); + return; + } + + if (forward) + { + if (pos <= editor().selectionEnd || pos > editor().value.length) + return false; + + do // TODO: test code for endless loops + { + this.executeCommand("cmd_selectCharNext", 1); + } + while ( editor().selectionEnd != pos ); + } + else + { + if (pos >= editor().selectionStart || pos < 0) + return false; + + do // TODO: test code for endless loops + { + this.executeCommand("cmd_selectCharPrevious", 1); + } + while ( editor().selectionStart != pos ); + } + }, + + // returns the position of char + findCharForward: function (char, count) + { + if (!editor()) + return -1; + + last_findChar = char; + last_findChar_func = this.findCharForward; + + var text = editor().value; + if (!typeof count == "number" || count < 1) + count = 1; + + for (var i = editor().selectionEnd + 1; i < text.length; i++) + { + if (text[i] == "\n") + break; + if (text[i] == char) + count--; + if (count == 0) + return i + 1; // always position the cursor after the char + } + + vimperator.beep(); + return -1; + }, + + // returns the position of char + findCharBackward: function (char, count) + { + if (!editor()) + return -1; + + last_findChar = char; + last_findChar_func = this.findCharBackward; + + var text = editor().value; + if (!typeof count == "number" || count < 1) + count = 1; + + for (var i = editor().selectionStart - 1; i >= 0; i--) + { + if (text[i] == "\n") + break; + if (text[i] == char) + count--; + if (count == 0) + return i; + } + + vimperator.beep(); + return -1; + }, + + editWithExternalEditor: function () + { + var textBox = document.commandDispatcher.focusedElement; + var editor = vimperator.options["editor"]; + var args = []; + args = editor.split(" "); + if (args.length < 1) + { + vimperator.echoerr("no editor specified"); + return; + } + + try + { + var tmpfile = vimperator.io.createTempFile(); + } + catch (e) + { + vimperator.echoerr("Could not create temporary file: " + e.message); + return; + } + try + { + vimperator.io.writeFile(tmpfile, textBox.value); + } + catch (e) + { + vimperator.echoerr("Could not write to temporary file " + tmpfile.path + ": " + e.message); + return; + } + + var prog = args.shift(); + args.push(tmpfile.path) + + textBox.setAttribute("readonly", "true"); + var oldBg = textBox.style.backgroundColor; + var tmpBg = "yellow"; + textBox.style.backgroundColor = "#bbbbbb"; + var newThread = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0); + // TODO: save return value in v:shell_error + vimperator.callFunctionInThread(newThread, vimperator.run, [prog, args, true]); + textBox.removeAttribute("readonly"); + + + // if (v:shell_error != 0) + // { + // tmpBg = "red"; + // vimperator.echoerr("External editor returned with exit code " + retcode); + // } + // else + { + try + { + var val = vimperator.io.readFile(tmpfile); + textBox.value = val; + } + catch (e) + { + tmpBg = "red"; + vimperator.echoerr("Could not read from temporary file " + tmpfile.path + ": " + e.message); + } + } + + // blink the textbox after returning + var timeout = 100; + textBox.style.backgroundColor = tmpBg; + setTimeout( function () { + textBox.style.backgroundColor = oldBg; + setTimeout( function () { + textBox.style.backgroundColor = tmpBg; + setTimeout( function () { + textBox.style.backgroundColor = oldBg; + }, timeout); + }, timeout); + }, timeout); + + tmpfile.remove(false); + }, + + // Abbreviations {{{ + + abbreviations: { + __iterator__: function () + { + var tmpCmd; + for (var lhs in abbrev) + { + for (var i = 0; i < abbrev[lhs].length; i++) + { + tmpCmd = (abbrev[lhs][i][0] == "!") ? "abbreviate" : abbrev[lhs][i][0] + "abbrev"; + yield (tmpCmd + " " + lhs + " " + abbrev[lhs][i][1] + "\n"); } } } + }, - if (!flagFound) + // filter is i, c or "!" (insert or command abbreviations or both) + listAbbreviations: function (filter, lhs) + { + if (lhs) // list only that one { - vimperator.echoerr("No abbreviations found"); - return; - } - list += "
" + abbrev[tmplhs][i][0] + " " + vimperator.util.escapeHTML(tmplhs) + " " + vimperator.util.escapeHTML(abbrev[tmplhs][i][1]) + "
"; - vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); - } - } - - this.addAbbreviation = function (filter, lhs, rhs) - { - if (!abbrev[lhs]) - { - abbrev[lhs] = []; - abbrev[lhs][0] = [filter, rhs]; - return; - } - - if (filter == "!") - { - if (abbrev[lhs][1]) - abbrev[lhs][1] = ""; - abbrev[lhs][0] = [filter, rhs]; - return; - } - - for (var i = 0; i < abbrev[lhs].length; i++) - { - if (abbrev[lhs][i][1] == rhs) - { - if (abbrev[lhs][i][0] == filter) + if (abbrev[lhs]) { - abbrev[lhs][i] = [filter, rhs]; + for (var i = 0; i < abbrev[lhs].length; i++) + { + if (abbrev[lhs][i][0] == filter) + vimperator.echo(abbrev[lhs][i][0] + " " + lhs + " " + abbrev[lhs][i][1]); + return true; + } + } + vimperator.echoerr("No abbreviations found"); + return false; + } + else // list all (for that filter {i,c,!}) + { + var flagFound = false; + var searchFilter = (filter == "!") ? "!ci" : filter + "!"; // ! -> list all, on c or i ! matches too) + var list = ""; + for (var tmplhs in abbrev) + { + for (var i = 0; i < abbrev[tmplhs].length; i++) + { + if (searchFilter.indexOf(abbrev[tmplhs][i][0]) > -1) + { + if (!flagFound) + flagFound = true; + + list += ""; + list += ""; + list += ""; + list += ""; + list += ""; + } + } + } + + if (!flagFound) + { + vimperator.echoerr("No abbreviations found"); return; } - else + list += "
" + abbrev[tmplhs][i][0] + " " + vimperator.util.escapeHTML(tmplhs) + " " + vimperator.util.escapeHTML(abbrev[tmplhs][i][1]) + "
"; + vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); + } + }, + + addAbbreviation: function (filter, lhs, rhs) + { + if (!abbrev[lhs]) + { + abbrev[lhs] = []; + abbrev[lhs][0] = [filter, rhs]; + return; + } + + if (filter == "!") + { + if (abbrev[lhs][1]) + abbrev[lhs][1] = ""; + abbrev[lhs][0] = [filter, rhs]; + return; + } + + for (var i = 0; i < abbrev[lhs].length; i++) + { + if (abbrev[lhs][i][1] == rhs) { - if (abbrev[lhs][i][0] != "!") + if (abbrev[lhs][i][0] == filter) { - if (abbrev[lhs][1]) - abbrev[lhs][1] = ""; - abbrev[lhs][0] = ["!", rhs]; + abbrev[lhs][i] = [filter, rhs]; return; } else { - return; + if (abbrev[lhs][i][0] != "!") + { + if (abbrev[lhs][1]) + abbrev[lhs][1] = ""; + abbrev[lhs][0] = ["!", rhs]; + return; + } + else + { + return; + } } } } - } - if (abbrev[lhs][0][0] == "!") - { - var tmpOpp = ("i" == filter) ? "c" : "i"; - abbrev[lhs][1] = [tmpOpp, abbrev[lhs][0][1]]; - abbrev[lhs][0] = [filter, rhs]; + if (abbrev[lhs][0][0] == "!") + { + var tmpOpp = ("i" == filter) ? "c" : "i"; + abbrev[lhs][1] = [tmpOpp, abbrev[lhs][0][1]]; + abbrev[lhs][0] = [filter, rhs]; + return; + } + + if (abbrev[lhs][0][0] != filter) + abbrev[lhs][1] = [filter, rhs]; + else + abbrev[lhs][0] = [filter, rhs]; + return; - } - if (abbrev[lhs][0][0] != filter) - abbrev[lhs][1] = [filter, rhs]; - else - abbrev[lhs][0] = [filter, rhs]; + // System above: + // filter == ! delete all, and set first (END) + // + // if filter == ! remove all and add it as only END + // + // variant 1: rhs matches anywere in loop + // + // 1 mod matches anywhere in loop + // a) simple replace and + // I) (maybe there's another rhs that matches? not possible) + // (when there's another item, it's opposite mod with different rhs) + // (so do nothing further but END) + // + // 2 mod does not match + // a) the opposite is there -> make a ! and put it as only and END + // (b) a ! is there. do nothing END) + // + // variant 2: rhs matches *no*were in loop and filter is c or i + // everykind of current combo is possible to 1 {c,i,!} or two {c and i} + // + // 1 mod is ! split into two i + c END + // 1 not !: opposite mode (first), add/change 'second' and END + // 1 not !: same mode (first), overwrite first this END + // + }, - return; - - // System above: - // filter == ! delete all, and set first (END) - // - // if filter == ! remove all and add it as only END - // - // variant 1: rhs matches anywere in loop - // - // 1 mod matches anywhere in loop - // a) simple replace and - // I) (maybe there's another rhs that matches? not possible) - // (when there's another item, it's opposite mod with different rhs) - // (so do nothing further but END) - // - // 2 mod does not match - // a) the opposite is there -> make a ! and put it as only and END - // (b) a ! is there. do nothing END) - // - // variant 2: rhs matches *no*were in loop and filter is c or i - // everykind of current combo is possible to 1 {c,i,!} or two {c and i} - // - // 1 mod is ! split into two i + c END - // 1 not !: opposite mode (first), add/change 'second' and END - // 1 not !: same mode (first), overwrite first this END - // - } - - this.removeAbbreviation = function (filter, lhs) - { - if (!lhs) + removeAbbreviation: function (filter, lhs) { - vimperator.echoerr("E474: Invalid argument"); - return false; - } + if (!lhs) + { + vimperator.echoerr("E474: Invalid argument"); + return false; + } - if (abbrev[lhs]) // abbrev exists + if (abbrev[lhs]) // abbrev exists + { + if (filter == "!") + { + abbrev[lhs] = ""; + return true; + } + else + { + if (!abbrev[lhs][1]) // only one exists + { + if (abbrev[lhs][0][0] == "!") // exists as ! -> no 'full' delete + { + abbrev[lhs][0][0] = (filter == "i") ? "c" : "i"; // ! - i = c; ! - c = i + return true; + } + else if (abbrev[lhs][0][0] == filter) + { + abbrev[lhs] = ""; + return true; + } + } + else // two abbrev's exists ( 'i' or 'c' (filter as well)) + { + if (abbrev[lhs][0][0] == "c" && filter == "c") + abbrev[lhs][0] = abbrev[lhs][1]; + + abbrev[lhs][1] = ""; + + return true; + } + } + } + + vimperator.echoerr("E24: No such abbreviation"); + return false; + }, + + removeAllAbbreviations: function (filter) { if (filter == "!") { - abbrev[lhs] = ""; - return true; + abbrev = {}; } else { - if (!abbrev[lhs][1]) // only one exists + for (var lhs in abbrev) { - if (abbrev[lhs][0][0] == "!") // exists as ! -> no 'full' delete + for (var i = 0; i < abbrev[lhs].length; i++) { - abbrev[lhs][0][0] = (filter == "i") ? "c" : "i"; // ! - i = c; ! - c = i - return true; + if (abbrev[lhs][i][0] == "!" || abbrev[lhs][i][0] == filter) + this.removeAbbreviation(filter, lhs); } - else if (abbrev[lhs][0][0] == filter) - { - abbrev[lhs] = ""; - return true; - } - } - else // two abbrev's exists ( 'i' or 'c' (filter as well)) - { - if (abbrev[lhs][0][0] == "c" && filter == "c") - abbrev[lhs][0] = abbrev[lhs][1]; - - abbrev[lhs][1] = ""; - - return true; } } - } + }, - vimperator.echoerr("E24: No such abbreviation"); - return false; - } + expandAbbreviation: function (filter) // try to find an candidate and replace accordingly + { + var textbox = editor(); + var text = textbox.value; + var currStart = textbox.selectionStart; + var currEnd = textbox.selectionEnd; + var foundWord = text.substring(0, currStart).replace(/^(.|\n)*?(\S+)$/m, "$2"); // get last word \b word boundary + if (!foundWord) + return true; - this.removeAllAbbreviations = function (filter) - { - if (filter == "!") - { - abbrev = {}; - } - else - { for (var lhs in abbrev) { - for (var i = 0; i < abbrev[lhs].length; i++) - { - if (abbrev[lhs][i][0] == "!" || abbrev[lhs][i][0] == filter) - this.removeAbbreviation(filter, lhs); - } - } - } - } - - this.expandAbbreviation = function (filter) // try to find an candidate and replace accordingly - { - var textbox = editor(); - var text = textbox.value; - var currStart = textbox.selectionStart; - var currEnd = textbox.selectionEnd; - var foundWord = text.substring(0, currStart).replace(/^(.|\n)*?(\S+)$/m, "$2"); // get last word \b word boundary - if (!foundWord) - return true; - - for (var lhs in abbrev) - { - for (var i = 0; i < abbrev[lhs].length; i++) - { - if (lhs == foundWord && (abbrev[lhs][i][0] == filter || abbrev[lhs][i][0] == "!")) + for (var i = 0; i < abbrev[lhs].length; i++) { - // if found, replace accordingly - var len = foundWord.length; - var abbr_text = abbrev[lhs][i][1]; - text = text.substring(0, currStart - len) + abbr_text + text.substring(currStart); - textbox.value = text; - textbox.selectionStart = currStart - len + abbr_text.length; - textbox.selectionEnd = currEnd - len + abbr_text.length; - break; + if (lhs == foundWord && (abbrev[lhs][i][0] == filter || abbrev[lhs][i][0] == "!")) + { + // if found, replace accordingly + var len = foundWord.length; + var abbr_text = abbrev[lhs][i][1]; + text = text.substring(0, currStart - len) + abbr_text + text.substring(currStart); + textbox.value = text; + textbox.selectionStart = currStart - len + abbr_text.length; + textbox.selectionEnd = currEnd - len + abbr_text.length; + break; + } } - } - } - return true; - } //}}} + } + return true; + } //}}} + + }; } //}}} // vim: set fdm=marker sw=4 ts=4 et: diff --git a/content/find.js b/content/find.js index efe01889..8cfd167c 100644 --- a/content/find.js +++ b/content/find.js @@ -39,7 +39,8 @@ the terms of any one of the MPL, the GPL or the LGPL. // make sure you only create this object when the "vimperator" object is ready vimperator.Search = function () //{{{ { - var self = this; // needed for callbacks since "this" is the "vimperator" object in a callback + // FIXME: + //var self = this; // needed for callbacks since "this" is the "vimperator" object in a callback var found = false; // true if the last search was successful var backwards = false; // currently searching backwards var search_string = ""; // current search string (without modifiers) @@ -51,13 +52,13 @@ vimperator.Search = function () //{{{ var links_only = false; // search is limited to link text only // Event handlers for search - closure is needed - vimperator.registerCallback("change", vimperator.modes.SEARCH_FORWARD, function (command) { self.searchKeyPressed(command); }); - vimperator.registerCallback("submit", vimperator.modes.SEARCH_FORWARD, function (command) { self.searchSubmitted(command); }); - vimperator.registerCallback("cancel", vimperator.modes.SEARCH_FORWARD, function () { self.searchCanceled(); }); + vimperator.registerCallback("change", vimperator.modes.SEARCH_FORWARD, function (command) { vimperator.search.searchKeyPressed(command); }); + vimperator.registerCallback("submit", vimperator.modes.SEARCH_FORWARD, function (command) { vimperator.search.searchSubmitted(command); }); + vimperator.registerCallback("cancel", vimperator.modes.SEARCH_FORWARD, function () { vimperator.search.searchCanceled(); }); // TODO: allow advanced modes in register/triggerCallback - vimperator.registerCallback("change", vimperator.modes.SEARCH_BACKWARD, function (command) { self.searchKeyPressed(command); }); - vimperator.registerCallback("submit", vimperator.modes.SEARCH_BACKWARD, function (command) { self.searchSubmitted(command); }); - vimperator.registerCallback("cancel", vimperator.modes.SEARCH_BACKWARD, function () { self.searchCanceled(); }); + vimperator.registerCallback("change", vimperator.modes.SEARCH_BACKWARD, function (command) { vimperator.search.searchKeyPressed(command); }); + vimperator.registerCallback("submit", vimperator.modes.SEARCH_BACKWARD, function (command) { vimperator.search.searchSubmitted(command); }); + vimperator.registerCallback("cancel", vimperator.modes.SEARCH_BACKWARD, function () { vimperator.search.searchCanceled(); }); // set search_string, search_pattern, case_sensitive, links_only function processUserPattern(pattern) @@ -104,158 +105,161 @@ vimperator.Search = function () //{{{ search_string = pattern; } - // Called when the search dialog is asked for - // If you omit "mode", it will default to forward searching - this.openSearchDialog = function (mode) - { - if (mode == vimperator.modes.SEARCH_BACKWARD) + return { + + // Called when the search dialog is asked for + // If you omit "mode", it will default to forward searching + openSearchDialog: function (mode) { - vimperator.commandline.open("?", "", vimperator.modes.SEARCH_BACKWARD); - backwards = true; - } - else + if (mode == vimperator.modes.SEARCH_BACKWARD) + { + vimperator.commandline.open("?", "", vimperator.modes.SEARCH_BACKWARD); + backwards = true; + } + else + { + vimperator.commandline.open("/", "", vimperator.modes.SEARCH_FORWARD); + backwards = false; + } + + // TODO: focus the top of the currently visible screen + }, + + // Finds text in a page + // TODO: backwards seems impossible i fear :( + find: function (str, backwards) { - vimperator.commandline.open("/", "", vimperator.modes.SEARCH_FORWARD); - backwards = false; - } + var fastFind = getBrowser().fastFind; - // TODO: focus the top of the currently visible screen - } + processUserPattern(str); - // Finds text in a page - // TODO: backwards seems impossible i fear :( - this.find = function (str, backwards) - { - var fastFind = getBrowser().fastFind; + fastFind.caseSensitive = case_sensitive; + found = fastFind.find(search_string, links_only) != Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND; - processUserPattern(str); + if (!found) + vimperator.echoerr("E486: Pattern not found: " + search_pattern); - fastFind.caseSensitive = case_sensitive; - found = fastFind.find(search_string, links_only) != Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND; + return found; + }, - if (!found) - vimperator.echoerr("E486: Pattern not found: " + search_pattern); - - return found; - } - - // Called when the current search needs to be repeated - this.findAgain = function (reverse) - { - // this hack is needed to make n/N work with the correct string, if - // we typed /foo after the original search. Since searchString is - // readonly we have to call find() again to update it. - if (getBrowser().fastFind.searchString != last_search_string) - this.find(last_search_string, false); - - var up = reverse ? !last_search_backwards : last_search_backwards; - var result = getBrowser().fastFind.findAgain(up, links_only); - - if (result == Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND) + // Called when the current search needs to be repeated + findAgain: function (reverse) { - vimperator.echoerr("E486: Pattern not found: " + last_search_pattern); - } - else if (result == Components.interfaces.nsITypeAheadFind.FIND_WRAPPED) + // this hack is needed to make n/N work with the correct string, if + // we typed /foo after the original search. Since searchString is + // readonly we have to call find() again to update it. + if (getBrowser().fastFind.searchString != last_search_string) + this.find(last_search_string, false); + + var up = reverse ? !last_search_backwards : last_search_backwards; + var result = getBrowser().fastFind.findAgain(up, links_only); + + if (result == Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND) + { + vimperator.echoerr("E486: Pattern not found: " + last_search_pattern); + } + else if (result == Components.interfaces.nsITypeAheadFind.FIND_WRAPPED) + { + // hack needed, because wrapping causes a "scroll" event which clears + // our command line + setTimeout(function () { + if (up) + vimperator.commandline.echo("search hit TOP, continuing at BOTTOM", vimperator.commandline.HL_WARNING); + else + vimperator.commandline.echo("search hit BOTTOM, continuing at TOP", vimperator.commandline.HL_WARNING); + }, 0); + } + else + { + vimperator.echo((up ? "?" : "/") + last_search_pattern, null, vimperator.commandline.FORCE_SINGLELINE); + + if (vimperator.options["hlsearch"]) + this.highlight(last_search_string); + } + }, + + // Called when the user types a key in the search dialog. Triggers a find attempt if 'incsearch' is set + searchKeyPressed: function (command) { - // hack needed, because wrapping causes a "scroll" event which clears - // our command line - setTimeout(function () { - if (up) - vimperator.commandline.echo("search hit TOP, continuing at BOTTOM", vimperator.commandline.HL_WARNING); - else - vimperator.commandline.echo("search hit BOTTOM, continuing at TOP", vimperator.commandline.HL_WARNING); - }, 0); - } - else + if (vimperator.options["incsearch"]) + this.find(command, backwards); + }, + + // Called when the enter key is pressed to trigger a search + // use forced_direction if you call this function directly + searchSubmitted: function (command, forced_backward) { - vimperator.echo((up ? "?" : "/") + last_search_pattern, null, vimperator.commandline.FORCE_SINGLELINE); + if (typeof forced_backward === "boolean") + backwards = forced_backward; + + // use the last pattern if none specified + if (!command) + command = last_search_pattern; + + this.clear(); + this.find(command, backwards); + + last_search_backwards = backwards; + last_search_pattern = command.replace(backwards ? /\?.*/ : /\/.*/, ""); // XXX + last_search_string = search_string; + + // TODO: move to find() when reverse incremental searching is kludged in + // need to find again for reverse searching + if (backwards) + setTimeout(function () { self.findAgain(false); }, 0); if (vimperator.options["hlsearch"]) - this.highlight(last_search_string); - } - } + this.highlight(search_string); - // Called when the user types a key in the search dialog. Triggers a find attempt if 'incsearch' is set - this.searchKeyPressed = function (command) - { - if (vimperator.options["incsearch"]) - this.find(command, backwards); - } + vimperator.modes.reset(); + }, - // Called when the enter key is pressed to trigger a search - // use forced_direction if you call this function directly - this.searchSubmitted = function (command, forced_backward) - { - if (typeof forced_backward === "boolean") - backwards = forced_backward; - - // use the last pattern if none specified - if (!command) - command = last_search_pattern; - - this.clear(); - this.find(command, backwards); - - last_search_backwards = backwards; - last_search_pattern = command.replace(backwards ? /\?.*/ : /\/.*/, ""); // XXX - last_search_string = search_string; - - // TODO: move to find() when reverse incremental searching is kludged in - // need to find again for reverse searching - if (backwards) - setTimeout(function () { self.findAgain(false); }, 0); - - if (vimperator.options["hlsearch"]) - this.highlight(search_string); - - vimperator.modes.reset(); - } - - // Called when the search is canceled - for example if someone presses - // escape while typing a search - this.searchCanceled = function () - { - this.clear(); - // TODO: code to reposition the document to the place before search started - } - - // this is not dependent on the value of 'hlsearch' - this.highlight = function (text) - { - // already highlighted? - if (window.content.document.getElementsByClassName("__mozilla-findbar-search").length > 0) - return; - - if (!text) - text = last_search_string; - - gFindBar._setCaseSensitivity(case_sensitive); - gFindBar._highlightDoc("white", "black", text); - - // TODO: seems fast enough for now...just - (function (win) + // Called when the search is canceled - for example if someone presses + // escape while typing a search + searchCanceled: function () { - for (var i = 0; i < win.frames.length; i++) - arguments.callee(win.frames[i]); - var spans = window.content.document.getElementsByClassName("__mozilla-findbar-search"); - for (var i = 0; i < spans.length; i++) - spans[i].setAttribute("style", vimperator.options["hlsearchstyle"]); - })(window.content); + this.clear(); + // TODO: code to reposition the document to the place before search started + }, - // recreate selection since _highlightDoc collapses the selection backwards - getBrowser().fastFind.findAgain(false, links_only); + // this is not dependent on the value of 'hlsearch' + highlight: function (text) + { + // already highlighted? + if (window.content.document.getElementsByClassName("__mozilla-findbar-search").length > 0) + return; - // TODO: remove highlighting from non-link matches (HTML - A/AREA with href attribute; XML - Xlink [type="simple"]) - } + if (!text) + text = last_search_string; - this.clear = function () - { - gFindBar._highlightDoc(); - // need to manually collapse the selection if the document is not - // highlighted - getBrowser().fastFind.collapseSelection(); - } + gFindBar._setCaseSensitivity(case_sensitive); + gFindBar._highlightDoc("white", "black", text); + // TODO: seems fast enough for now...just + (function (win) + { + for (var i = 0; i < win.frames.length; i++) + arguments.callee(win.frames[i]); + var spans = window.content.document.getElementsByClassName("__mozilla-findbar-search"); + for (var i = 0; i < spans.length; i++) + spans[i].setAttribute("style", vimperator.options["hlsearchstyle"]); + })(window.content); + + // recreate selection since _highlightDoc collapses the selection backwards + getBrowser().fastFind.findAgain(false, links_only); + + // TODO: remove highlighting from non-link matches (HTML - A/AREA with href attribute; XML - Xlink [type="simple"]) + }, + + clear: function () + { + gFindBar._highlightDoc(); + // need to manually collapse the selection if the document is not + // highlighted + getBrowser().fastFind.collapseSelection(); + } + + }; } //}}} // vim: set fdm=marker sw=4 ts=4 et: diff --git a/content/io.js b/content/io.js index 3c641a74..35607029 100644 --- a/content/io.js +++ b/content/io.js @@ -234,7 +234,7 @@ vimperator.IO = function () ocstream.close(); ofstream.close(); } - } + }; } // vim: set fdm=marker sw=4 ts=4 et: diff --git a/content/modes.js b/content/modes.js index 3805ceb8..56bc337c 100644 --- a/content/modes.js +++ b/content/modes.js @@ -92,9 +92,11 @@ vimperator.modes = (function () { // clear any selection made var selection = window.content.getSelection(); - try { // a simple if (selection) does not work + try + { // a simple if (selection) does not work selection.collapseToStart(); - } catch (e) { } + } + catch (e) { } } else vimperator.editor.unselectText(); @@ -122,6 +124,7 @@ vimperator.modes = (function () } return { + // main modes, only one should ever be active NONE: 0, NORMAL: 1 << 0, @@ -219,7 +222,8 @@ vimperator.modes = (function () set extended(value) { extended = value; this.show(); } - } + + }; })(); // vim: set fdm=marker sw=4 ts=4 et: diff --git a/content/ui.js b/content/ui.js index f0e9b1b8..2b9cc3d4 100644 --- a/content/ui.js +++ b/content/ui.js @@ -236,603 +236,607 @@ vimperator.CommandLine = function () //{{{ ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - this.HL_NORMAL = "hl-Normal"; - this.HL_ERRORMSG = "hl-ErrorMsg"; - this.HL_MODEMSG = "hl-ModeMsg"; - this.HL_MOREMSG = "hl-MoreMsg"; - this.HL_QUESTION = "hl-Question"; - this.HL_WARNING = "hl-Warning"; + return { - // not yet used - this.FORCE_MULTILINE = 1 << 0; - this.FORCE_SINGLELINE = 1 << 1; - this.DISALLOW_MULTILINE = 1 << 2; // if an echo() should try to use the single line, - // but output nothing when the MOW is open; when also - // FORCE_MULTILINE is given, FORCE_MULTILINE takes precedence - this.APPEND_TO_MESSAGES = 1 << 3; // will show the string in :messages + HL_NORMAL : "hl-Normal", + HL_ERRORMSG: "hl-ErrorMsg", + HL_MODEMSG : "hl-ModeMsg", + HL_MOREMSG : "hl-MoreMsg", + HL_QUESTION: "hl-Question", + HL_WARNING : "hl-Warning", - this.getCommand = function () - { - return command_widget.value; - }; + // not yet used + FORCE_MULTILINE : 1 << 0, + FORCE_SINGLELINE : 1 << 1, + DISALLOW_MULTILINE : 1 << 2, // if an echo() should try to use the single line, + // but output nothing when the MOW is open; when also + // FORCE_MULTILINE is given, FORCE_MULTILINE takes precedence + APPEND_TO_MESSAGES : 1 << 3, // will show the string in :messages - this.open = function (prompt, cmd, ext_mode) - { - // save the current prompts, we need it later if the command widget - // receives focus without calling the this.open() method - cur_prompt = prompt || ""; - cur_command = cmd || ""; - cur_extended_mode = ext_mode || null; - - history_index = UNINITIALIZED; - completion_index = UNINITIALIZED; - - // save the mode, because we need to restore it - old_mode = vimperator.mode; - old_extended_mode = vimperator.mode.extended; - vimperator.modes.set(vimperator.modes.COMMAND_LINE, cur_extended_mode); - setHighlightGroup(this.HL_NORMAL); - setPrompt(cur_prompt); - setCommand(cur_command); - - command_widget.focus(); - }; - - // normally used when pressing esc, does not execute a command - this.close = function () - { - var res = vimperator.triggerCallback("cancel", cur_extended_mode); - history.add(this.getCommand()); - vimperator.statusline.updateProgress(""); // we may have a "match x of y" visible - this.clear(); - } - - this.clear = function () - { - multiline_input_widget.collapsed = true; - multiline_output_widget.collapsed = true; - completionlist.hide(); - - setLine("", this.HL_NORMAL); - }; - - // TODO: add :messages entry - // vimperator.echo uses different order of flags as it omits the hightlight group, change v.commandline.echo argument order? --mst - this.echo = function (str, highlight_group, flags) - { - var focused = document.commandDispatcher.focusedElement; - if (focused && focused == command_widget.inputField || focused == multiline_input_widget.inputField) - return false; - - highlight_group = highlight_group || this.HL_NORMAL; - - var where = setLine; - if (flags & this.FORCE_MULTILINE) - where = setMultiline; - else if (flags & this.FORCE_SINGLELINE) - where = setLine; - else if (!multiline_output_widget.collapsed) + getCommand: function () { - if (flags & this.DISALLOW_MULTILINE) - where = null; - else + return command_widget.value; + }, + + open: function (prompt, cmd, ext_mode) + { + // save the current prompts, we need it later if the command widget + // receives focus without calling the this.open() method + cur_prompt = prompt || ""; + cur_command = cmd || ""; + cur_extended_mode = ext_mode || null; + + history_index = UNINITIALIZED; + completion_index = UNINITIALIZED; + + // save the mode, because we need to restore it + old_mode = vimperator.mode; + old_extended_mode = vimperator.mode.extended; + vimperator.modes.set(vimperator.modes.COMMAND_LINE, cur_extended_mode); + setHighlightGroup(this.HL_NORMAL); + setPrompt(cur_prompt); + setCommand(cur_command); + + command_widget.focus(); + }, + + // normally used when pressing esc, does not execute a command + close: function () + { + var res = vimperator.triggerCallback("cancel", cur_extended_mode); + history.add(this.getCommand()); + vimperator.statusline.updateProgress(""); // we may have a "match x of y" visible + this.clear(); + }, + + clear: function () + { + multiline_input_widget.collapsed = true; + multiline_output_widget.collapsed = true; + completionlist.hide(); + + setLine("", this.HL_NORMAL); + }, + + // TODO: add :messages entry + // vimperator.echo uses different order of flags as it omits the hightlight group, change v.commandline.echo argument order? --mst + echo: function (str, highlight_group, flags) + { + var focused = document.commandDispatcher.focusedElement; + if (focused && focused == command_widget.inputField || focused == multiline_input_widget.inputField) + return false; + + highlight_group = highlight_group || this.HL_NORMAL; + + var where = setLine; + if (flags & this.FORCE_MULTILINE) where = setMultiline; - } - else if (/\n|/.test(str)) - where = setMultiline; - - if (where) - where(str, highlight_group); - - cur_extended_mode = null; - - return true; - }; - - // this will prompt the user for a string - // vimperator.commandline.input("(s)ave or (o)pen the file?") - this.input = function (str) - { - // TODO: unfinished, need to find out how/if we can block the execution of code - // to make this code synchronous or at least use a callback - setLine(str, this.HL_QUESTION); - command_widget.focus(); - return "not implemented"; - }; - - // reads a multi line input and returns the string once the last line matches - // @param until_regexp - this.inputMultiline = function (until_regexp, callback_func) - { - // save the mode, because we need to restore it - old_mode = vimperator.mode; - old_extended_mode = vimperator.mode.extended; - vimperator.modes.set(vimperator.modes.COMMAND_LINE, vimperator.modes.INPUT_MULTILINE); - - // save the arguments, they are needed in the event handler onEvent - multiline_regexp = until_regexp; - multiline_callback = callback_func; - - multiline_input_widget.collapsed = false; - multiline_input_widget.value = ""; - autosizeMultilineInputWidget(); - - setTimeout(function () { - multiline_input_widget.focus(); - }, 10); - }; - - this.onEvent = function (event) - { - var command = this.getCommand(); - - if (event.type == "blur") - { - // prevent losing focus, there should be a better way, but it just didn't work otherwise - setTimeout(function () { - if (vimperator.mode == vimperator.modes.COMMAND_LINE && - !(vimperator.modes.extended & vimperator.modes.INPUT_MULTILINE) && - !(vimperator.modes.extended & vimperator.modes.OUTPUT_MULTILINE)) - command_widget.inputField.focus(); - }, 0); - } - else if (event.type == "focus") - { - if (!cur_extended_mode && event.target == command_widget.inputField) - event.target.blur(); - } - else if (event.type == "input") - { - vimperator.triggerCallback("change", cur_extended_mode, command); - } - else if (event.type == "keypress") - { - if (!cur_extended_mode) - return; - - var key = vimperator.events.toString(event); - - // user pressed ENTER to carry out a command - // user pressing ESCAPE is handled in the global onEscape - if (vimperator.events.isAcceptKey(key)) + else if (flags & this.FORCE_SINGLELINE) + where = setLine; + else if (!multiline_output_widget.collapsed) { - var mode = cur_extended_mode; // save it here, as setMode() resets it - history.add(command); - vimperator.modes.reset(true); //FIXME: use mode stack - completionlist.hide(); - vimperator.focusContent(false); - vimperator.statusline.updateProgress(""); // we may have a "match x of y" visible - return vimperator.triggerCallback("submit", mode, command); + if (flags & this.DISALLOW_MULTILINE) + where = null; + else + where = setMultiline; } + else if (/\n|/.test(str)) + where = setMultiline; + if (where) + where(str, highlight_group); - // user pressed UP or DOWN arrow to cycle history completion - else if (key == "" || key == "") + cur_extended_mode = null; + + return true; + }, + + // this will prompt the user for a string + // vimperator.commandline.input("(s)ave or (o)pen the file?") + input: function (str) + { + // TODO: unfinished, need to find out how/if we can block the execution of code + // to make this code synchronous or at least use a callback + setLine(str, this.HL_QUESTION); + command_widget.focus(); + return "not implemented"; + }, + + // reads a multi line input and returns the string once the last line matches + // @param until_regexp + inputMultiline: function (until_regexp, callback_func) + { + // save the mode, because we need to restore it + old_mode = vimperator.mode; + old_extended_mode = vimperator.mode.extended; + vimperator.modes.set(vimperator.modes.COMMAND_LINE, vimperator.modes.INPUT_MULTILINE); + + // save the arguments, they are needed in the event handler onEvent + multiline_regexp = until_regexp; + multiline_callback = callback_func; + + multiline_input_widget.collapsed = false; + multiline_input_widget.value = ""; + autosizeMultilineInputWidget(); + + setTimeout(function () { + multiline_input_widget.focus(); + }, 10); + }, + + onEvent: function (event) + { + var command = this.getCommand(); + + if (event.type == "blur") { - var lines = history.get(); + // prevent losing focus, there should be a better way, but it just didn't work otherwise + setTimeout(function () { + if (vimperator.mode == vimperator.modes.COMMAND_LINE && + !(vimperator.modes.extended & vimperator.modes.INPUT_MULTILINE) && + !(vimperator.modes.extended & vimperator.modes.OUTPUT_MULTILINE)) + command_widget.inputField.focus(); + }, 0); + } + else if (event.type == "focus") + { + if (!cur_extended_mode && event.target == command_widget.inputField) + event.target.blur(); + } + else if (event.type == "input") + { + vimperator.triggerCallback("change", cur_extended_mode, command); + } + else if (event.type == "keypress") + { + if (!cur_extended_mode) + return; - event.preventDefault(); - event.stopPropagation(); + var key = vimperator.events.toString(event); - // always reset the tab completion if we use up/down keys - completion_index = UNINITIALIZED; - - // save 'start' position for iterating through the history - if (history_index == UNINITIALIZED) + // user pressed ENTER to carry out a command + // user pressing ESCAPE is handled in the global onEscape + if (vimperator.events.isAcceptKey(key)) { - history_index = lines.length; - history_start = command; + var mode = cur_extended_mode; // save it here, as setMode() resets it + history.add(command); + vimperator.modes.reset(true); //FIXME: use mode stack + completionlist.hide(); + vimperator.focusContent(false); + vimperator.statusline.updateProgress(""); // we may have a "match x of y" visible + return vimperator.triggerCallback("submit", mode, command); } - // search the history for the first item matching the current - // commandline string - while (history_index >= -1 && history_index <= lines.length) + + // user pressed UP or DOWN arrow to cycle history completion + else if (key == "" || key == "") { - key == "" ? history_index-- : history_index++; + var lines = history.get(); - // user pressed DOWN when there is no newer history item - if (history_index == lines.length) - { - setCommand(history_start); - vimperator.triggerCallback("change", cur_extended_mode, this.getCommand()); - return; - } + event.preventDefault(); + event.stopPropagation(); - // cannot go past history start/end - if (history_index <= -1) - { - history_index = 0; - vimperator.beep(); - break; - } - if (history_index >= lines.length + 1) + // always reset the tab completion if we use up/down keys + completion_index = UNINITIALIZED; + + // save 'start' position for iterating through the history + if (history_index == UNINITIALIZED) { history_index = lines.length; - vimperator.beep(); - break; + history_start = command; } - if (lines[history_index].indexOf(history_start) == 0) + // search the history for the first item matching the current + // commandline string + while (history_index >= -1 && history_index <= lines.length) { - setCommand(lines[history_index]); - vimperator.triggerCallback("change", cur_extended_mode, this.getCommand()); + key == "" ? history_index-- : history_index++; + + // user pressed DOWN when there is no newer history item + if (history_index == lines.length) + { + setCommand(history_start); + vimperator.triggerCallback("change", cur_extended_mode, this.getCommand()); + return; + } + + // cannot go past history start/end + if (history_index <= -1) + { + history_index = 0; + vimperator.beep(); + break; + } + if (history_index >= lines.length + 1) + { + history_index = lines.length; + vimperator.beep(); + break; + } + + if (lines[history_index].indexOf(history_start) == 0) + { + setCommand(lines[history_index]); + vimperator.triggerCallback("change", cur_extended_mode, this.getCommand()); + return; + } + } + } + + // user pressed TAB to get completions of a command + else if (key == "" || key == "") + { + //always reset our completion history so up/down keys will start with new values + history_index = UNINITIALIZED; + + // we need to build our completion list first + if (completion_index == UNINITIALIZED) + { + completion_start_index = 0; + + completion_index = -1; + wild_index = 0; + + completion_prefix = command.substring(0, command_widget.selectionStart); + completion_postfix = command.substring(command_widget.selectionStart); + var res = vimperator.triggerCallback("complete", cur_extended_mode, completion_prefix); + if (res) + [completion_start_index, completions] = res; + + // sort the completion list + if (vimperator.options["wildoptions"].search(/\bsort\b/) > -1) + { + completions.sort(function (a, b) { + if (a[0] < b[0]) + return -1; + else if (a[0] > b[0]) + return 1; + else + return 0; + }); + } + } + + if (completions.length == 0) + { + vimperator.beep(); + // prevent tab from moving to the next field + event.preventDefault(); + event.stopPropagation(); return; } - } - } - // user pressed TAB to get completions of a command - else if (key == "" || key == "") - { - //always reset our completion history so up/down keys will start with new values - history_index = UNINITIALIZED; + var wim = vimperator.options["wildmode"].split(/,/); + var has_list = false; + var longest = false; + var full = false; + var wildtype = wim[wild_index++] || wim[wim.length - 1]; + if (wildtype == "list" || wildtype == "list:full" || wildtype == "list:longest") + has_list = true; + if (wildtype == "longest" || wildtype == "list:longest") + longest = true; + else if (wildtype == "full" || wildtype == "list:full") + full = true; - // we need to build our completion list first - if (completion_index == UNINITIALIZED) - { - completion_start_index = 0; - - completion_index = -1; - wild_index = 0; - - completion_prefix = command.substring(0, command_widget.selectionStart); - completion_postfix = command.substring(command_widget.selectionStart); - var res = vimperator.triggerCallback("complete", cur_extended_mode, completion_prefix); - if (res) - [completion_start_index, completions] = res; - - // sort the completion list - if (vimperator.options["wildoptions"].search(/\bsort\b/) > -1) + // show the list + if (has_list) { - completions.sort(function (a, b) { - if (a[0] < b[0]) - return -1; - else if (a[0] > b[0]) - return 1; - else - return 0; - }); + if (completion_index < 0) + completionlist.show(completions); + else + completionlist.show(); + } + + if (full) + { + if (event.shiftKey) + { + completion_index--; + if (completion_index < -1) + completion_index = completions.length -1; + } + else + { + completion_index++; + if (completion_index >= completions.length) + completion_index = -1; + } + + vimperator.statusline.updateProgress("match " + (completion_index + 1) + " of " + completions.length); + // if the list is hidden, this function does nothing + completionlist.selectItem(completion_index); + } + + + if (completion_index == -1 && !longest) // wrapped around matches, reset command line + { + if (full && completions.length > 1) + { + setCommand(completion_prefix + completion_postfix); + } + } + else + { + if (longest && completions.length > 1) + var compl = vimperator.completion.get_longest_substring(); + else if (full) + var compl = completions[completion_index][0]; + else if (completions.length == 1) + var compl = completions[0][0]; + if (compl) + { + setCommand(command.substring(0, completion_start_index) + compl + completion_postfix); + command_widget.selectionStart = command_widget.selectionEnd = completion_start_index + compl.length; + + // 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 + // Needed for :source to grab another set of completions after a file/directory has been filled out + if (completions.length == 1 && !full) + completion_index = UNINITIALIZED; + } } - } - if (completions.length == 0) - { - vimperator.beep(); // prevent tab from moving to the next field event.preventDefault(); event.stopPropagation(); - return; } - - var wim = vimperator.options["wildmode"].split(/,/); - var has_list = false; - var longest = false; - var full = false; - var wildtype = wim[wild_index++] || wim[wim.length - 1]; - if (wildtype == "list" || wildtype == "list:full" || wildtype == "list:longest") - has_list = true; - if (wildtype == "longest" || wildtype == "list:longest") - longest = true; - else if (wildtype == "full" || wildtype == "list:full") - full = true; - - // show the list - if (has_list) + else if (key == "") { - if (completion_index < 0) - completionlist.show(completions); - else - completionlist.show(); - } + // reset the tab completion + completion_index = history_index = UNINITIALIZED; - if (full) - { - if (event.shiftKey) + // and blur the command line if there is no text left + if (command.length == 0) { - completion_index--; - if (completion_index < -1) - completion_index = completions.length -1; - } - else - { - completion_index++; - if (completion_index >= completions.length) - completion_index = -1; - } - - vimperator.statusline.updateProgress("match " + (completion_index + 1) + " of " + completions.length); - // if the list is hidden, this function does nothing - completionlist.selectItem(completion_index); - } - - - if (completion_index == -1 && !longest) // wrapped around matches, reset command line - { - if (full && completions.length > 1) - { - setCommand(completion_prefix + completion_postfix); + vimperator.triggerCallback("cancel", cur_extended_mode); + vimperator.modes.reset(); // FIXME: use mode stack } } - else + else // any other key { - if (longest && completions.length > 1) - var compl = vimperator.completion.get_longest_substring(); - else if (full) - var compl = completions[completion_index][0]; - else if (completions.length == 1) - var compl = completions[0][0]; - if (compl) - { - setCommand(command.substring(0, completion_start_index) + compl + completion_postfix); - command_widget.selectionStart = command_widget.selectionEnd = completion_start_index + compl.length; - - // 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 - // Needed for :source to grab another set of completions after a file/directory has been filled out - if (completions.length == 1 && !full) - completion_index = UNINITIALIZED; - } - } - - // prevent tab from moving to the next field - event.preventDefault(); - event.stopPropagation(); - } - else if (key == "") - { - // reset the tab completion - completion_index = history_index = UNINITIALIZED; - - // and blur the command line if there is no text left - if (command.length == 0) - { - vimperator.triggerCallback("cancel", cur_extended_mode); - vimperator.modes.reset(); // FIXME: use mode stack + // reset the tab completion + completion_index = history_index = UNINITIALIZED; } } - else // any other key - { - // reset the tab completion - completion_index = history_index = UNINITIALIZED; - } - } - } + }, - this.onMultilineInputEvent = function (event) - { - if (event.type == "keypress") + onMultilineInputEvent: function (event) { - var key = vimperator.events.toString(event); - if (vimperator.events.isAcceptKey(key)) + if (event.type == "keypress") { - var text = multiline_input_widget.value.substr(0, multiline_input_widget.selectionStart); - if (text.match(multiline_regexp)) + var key = vimperator.events.toString(event); + if (vimperator.events.isAcceptKey(key)) + { + var text = multiline_input_widget.value.substr(0, multiline_input_widget.selectionStart); + if (text.match(multiline_regexp)) + { + text = text.replace(multiline_regexp, ""); + vimperator.modes.set(old_mode, old_extended_mode); + multiline_input_widget.collapsed = true; + multiline_callback.call(this, text); + } + } + else if (vimperator.events.isCancelKey(key)) { - text = text.replace(multiline_regexp, ""); vimperator.modes.set(old_mode, old_extended_mode); multiline_input_widget.collapsed = true; - multiline_callback.call(this, text); } } - else if (vimperator.events.isCancelKey(key)) + else if (event.type == "blur") { - vimperator.modes.set(old_mode, old_extended_mode); - multiline_input_widget.collapsed = true; + if (vimperator.modes.extended & vimperator.modes.INPUT_MULTILINE) + setTimeout(function () { multiline_input_widget.inputField.focus(); }, 0); } - } - else if (event.type == "blur") + else if (event.type == "input") + { + autosizeMultilineInputWidget(); + } + }, + + // FIXME: if 'more' is set and the MOW is not scrollable we should still + // allow a down motion after an up rather than closing + onMultilineOutputEvent: function (event) { - if (vimperator.modes.extended & vimperator.modes.INPUT_MULTILINE) - setTimeout(function () { multiline_input_widget.inputField.focus(); }, 0); - } - else if (event.type == "input") - { - autosizeMultilineInputWidget(); - } - } + var win = multiline_output_widget.contentWindow; - // FIXME: if 'more' is set and the MOW is not scrollable we should still - // allow a down motion after an up rather than closing - this.onMultilineOutputEvent = function (event) - { - var win = multiline_output_widget.contentWindow; + var show_more_help_prompt = false; + var show_more_prompt = false; + var close_window = false; + var pass_event = false; - var show_more_help_prompt = false; - var show_more_prompt = false; - var close_window = false; - var pass_event = false; + function isScrollable() { return !win.scrollMaxY == 0; } - function isScrollable() { return !win.scrollMaxY == 0; } + function atEnd() { return win.scrollY / win.scrollMaxY >= 1; } - function atEnd() { return win.scrollY / win.scrollMaxY >= 1; } + var key = vimperator.events.toString(event); - var key = vimperator.events.toString(event); + switch (key) + { + case "": + return; // handled globally in events.js:onEscape() - switch (key) - { - case "": - return; // handled globally in events.js:onEscape() + case ":": + vimperator.commandline.open(":", "", vimperator.modes.EX); + return; - case ":": - vimperator.commandline.open(":", "", vimperator.modes.EX); - return; + // down a line + case "j": + case "": + if (vimperator.options["more"] && isScrollable()) + win.scrollByLines(1); + else + pass_event = true; + break; - // down a line - case "j": - case "": - if (vimperator.options["more"] && isScrollable()) - win.scrollByLines(1); - else - pass_event = true; - break; + case "": + case "": + case "": + if (vimperator.options["more"] && isScrollable() && !atEnd()) + win.scrollByLines(1); + else + close_window = true; // don't propagate the event for accept keys + break; - case "": - case "": - case "": - if (vimperator.options["more"] && isScrollable() && !atEnd()) - win.scrollByLines(1); - else - close_window = true; // don't propagate the event for accept keys - break; + // up a line + case "k": + case "": + case "": + if (vimperator.options["more"] && isScrollable()) + win.scrollByLines(-1); + else if (vimperator.options["more"] && !isScrollable()) + show_more_prompt = true; + else + pass_event = true; + break; - // up a line - case "k": - case "": - case "": - if (vimperator.options["more"] && isScrollable()) - win.scrollByLines(-1); - else if (vimperator.options["more"] && !isScrollable()) - show_more_prompt = true; - else - pass_event = true; - break; + // half page down + case "d": + if (vimperator.options["more"] && isScrollable()) + win.scrollBy(0, win.innerHeight / 2); + else + pass_event = true; + break; - // half page down - case "d": - if (vimperator.options["more"] && isScrollable()) - win.scrollBy(0, win.innerHeight / 2); - else - pass_event = true; - break; + case "": + if (event.originalTarget.className == "hl-URL buffer-list") + { + vimperator.tabs.select(parseInt(event.originalTarget.parentNode.parentNode.firstChild.textContent, 10) - 1); + close_window = true; + break; + } + else if (event.originalTarget.localName.toLowerCase() == "a") + { + vimperator.open(event.originalTarget.textContent); + break; + } + case "": // for those not owning a 3-button mouse + case "": + if (event.originalTarget.localName.toLowerCase() == "a") + { + var where = /\btabopen\b/.test(vimperator.options["activate"]) ? + vimperator.NEW_TAB : vimperator.NEW_BACKGROUND_TAB; + vimperator.open(event.originalTarget.textContent, where); + } + break; - case "": - if (event.originalTarget.className == "hl-URL buffer-list") - { - vimperator.tabs.select(parseInt(event.originalTarget.parentNode.parentNode.firstChild.textContent, 10) - 1); + // let firefox handle those to select table cells or show a context menu + case "": + case "": + case "": + break; + + // XXX: what's that for? --mst + case "": + if (/^(end|more(-help)?)-prompt$/.test(event.target.id)) + ; // fall through + else + break; + + // page down + case "f": + if (vimperator.options["more"] && isScrollable()) + win.scrollByPages(1); + else + pass_event = true; + break; + + case "": + case "": + if (vimperator.options["more"] && isScrollable() && !atEnd()) + win.scrollByPages(1); + else + pass_event = true; + break; + + // half page up + case "u": + // if (more and scrollable) + if (vimperator.options["more"] && isScrollable()) + win.scrollBy(0, -(win.innerHeight / 2)); + else + pass_event = true; + break; + + // page up + case "b": + if (vimperator.options["more"] && isScrollable()) + win.scrollByPages(-1); + else if (vimperator.options["more"] && !isScrollable()) + show_more_prompt = true; + else + pass_event = true; + break; + + case "": + if (vimperator.options["more"] && isScrollable()) + win.scrollByPages(-1); + else + pass_event = true; + break; + + // top of page + case "g": + if (vimperator.options["more"] && isScrollable()) + win.scrollTo(0, 0); + else if (vimperator.options["more"] && !isScrollable()) + show_more_prompt = true; + else + pass_event = true; + break; + + // bottom of page + case "G": + if (vimperator.options["more"] && isScrollable() && !atEnd()) + win.scrollTo(0, win.scrollMaxY); + else + pass_event = true; + break; + + // copy text to clipboard + case "": + vimperator.copyToClipboard(win.getSelection()); + break; + + // close the window + case "q": close_window = true; break; - } - else if (event.originalTarget.localName.toLowerCase() == "a") - { - vimperator.open(event.originalTarget.textContent); - break; - } - case "": // for those not owning a 3-button mouse - case "": - if (event.originalTarget.localName.toLowerCase() == "a") - { - var where = /\btabopen\b/.test(vimperator.options["activate"]) ? - vimperator.NEW_TAB : vimperator.NEW_BACKGROUND_TAB; - vimperator.open(event.originalTarget.textContent, where); - } - break; - // let firefox handle those to select table cells or show a context menu - case "": - case "": - case "": - break; + // unmapped key + default: + if (!vimperator.options["more"] || !isScrollable() || atEnd() || vimperator.events.isCancelKey(key)) + pass_event = true; + else + show_more_help_prompt = true; + } - // XXX: what's that for? --mst - case "": - if (/^(end|more(-help)?)-prompt$/.test(event.target.id)) - ; // fall through + if (pass_event || close_window) + { + // FIXME: use mode stack + vimperator.modes.reset(); + this.clear(); + + if (pass_event) + vimperator.events.onKeyPress(event); + } + else // set update the prompt string + { + if (show_more_help_prompt) + setLine("-- More -- SPACE/d/j: screen/page/line down, b/u/k: up, q: quit", this.HL_MOREMSG); + else if (show_more_prompt || (vimperator.options["more"] && isScrollable() && !atEnd())) + setLine("-- More --", this.HL_MOREMSG); else - break; + setLine("Press ENTER or type command to continue", this.HL_QUESTION); + } + }, - // page down - case "f": - if (vimperator.options["more"] && isScrollable()) - win.scrollByPages(1); - else - pass_event = true; - break; - - case "": - case "": - if (vimperator.options["more"] && isScrollable() && !atEnd()) - win.scrollByPages(1); - else - pass_event = true; - break; - - // half page up - case "u": - // if (more and scrollable) - if (vimperator.options["more"] && isScrollable()) - win.scrollBy(0, -(win.innerHeight / 2)); - else - pass_event = true; - break; - - // page up - case "b": - if (vimperator.options["more"] && isScrollable()) - win.scrollByPages(-1); - else if (vimperator.options["more"] && !isScrollable()) - show_more_prompt = true; - else - pass_event = true; - break; - - case "": - if (vimperator.options["more"] && isScrollable()) - win.scrollByPages(-1); - else - pass_event = true; - break; - - // top of page - case "g": - if (vimperator.options["more"] && isScrollable()) - win.scrollTo(0, 0); - else if (vimperator.options["more"] && !isScrollable()) - show_more_prompt = true; - else - pass_event = true; - break; - - // bottom of page - case "G": - if (vimperator.options["more"] && isScrollable() && !atEnd()) - win.scrollTo(0, win.scrollMaxY); - else - pass_event = true; - break; - - // copy text to clipboard - case "": - vimperator.copyToClipboard(win.getSelection()); - break; - - // close the window - case "q": - close_window = true; - break; - - // unmapped key - default: - if (!vimperator.options["more"] || !isScrollable() || atEnd() || vimperator.events.isCancelKey(key)) - pass_event = true; - else - show_more_help_prompt = true; - } - - if (pass_event || close_window) + // it would be better if we had a destructor in javascript ... + destroy: function () { - // FIXME: use mode stack - vimperator.modes.reset(); - this.clear(); - - if (pass_event) - vimperator.events.onKeyPress(event); + history.save(); } - else // set update the prompt string - { - if (show_more_help_prompt) - setLine("-- More -- SPACE/d/j: screen/page/line down, b/u/k: up, q: quit", this.HL_MOREMSG); - else if (show_more_prompt || (vimperator.options["more"] && isScrollable() && !atEnd())) - setLine("-- More --", this.HL_MOREMSG); - else - setLine("Press ENTER or type command to continue", this.HL_QUESTION); - } - } - // it would be better if we had a destructor in javascript ... - this.destroy = function () - { - history.save(); - } + }; //}}} } //}}} @@ -935,113 +939,117 @@ vimperator.InformationList = function (id, options) //{{{ ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - /** - * Show the completion list window - * - * @param compl: if null, only show the list with current entries, otherwise - * use entries of 'compl' to fill the list. - * Required format: [["left", "right"], ["another"], ["completion"]] - */ - this.show = function (compl) - { - //max_items = vimperator.options["previewheight"]; + return { - if (compl) + /** + * Show the completion list window + * + * @param compl: if null, only show the list with current entries, otherwise + * use entries of 'compl' to fill the list. + * Required format: [["left", "right"], ["another"], ["completion"]] + */ + show: function (compl) { - completions = compl; - fill(0); - } + //max_items = vimperator.options["previewheight"]; - var length = completions.length; - if (length > max_items) - length = max_items; - if (length >= min_items) - { - widget.setAttribute("rows", length.toString()); - widget.hidden = false; - return true; - } - else + if (compl) + { + completions = compl; + fill(0); + } + + var length = completions.length; + if (length > max_items) + length = max_items; + if (length >= min_items) + { + widget.setAttribute("rows", length.toString()); + widget.hidden = false; + return true; + } + else + { + widget.hidden = true; + return false; + } + }, + + hide: function () { widget.hidden = true; - return false; - } - } + }, - this.hide = function () - { - widget.hidden = true; - } - - this.visible = function () - { - return !widget.hidden; - } - - /** - * select index, refill list if necessary - */ - this.selectItem = function (index) - { - if (widget.hidden) - return; - - if (!incremental_fill) + visible: function () { - widget.selectedIndex = index; - return; - } + return !widget.hidden; + }, - // find start index - var new_offset = 0; - if (index >= list_offset + max_items - CONTEXT_LINES) - new_offset = index - max_items + CONTEXT_LINES + 1; - else if (index <= list_offset + CONTEXT_LINES) - new_offset = index - CONTEXT_LINES; - else - new_offset = list_offset; - - if (new_offset + max_items > completions.length) - new_offset = completions.length - max_items; - if (new_offset < 0) - new_offset = 0; - - // for speed reason: just remove old item, and add the new one at the end of the list - var items = widget.getElementsByTagName("listitem"); - if (new_offset == list_offset + 1) + /** + * select index, refill list if necessary + */ + selectItem: function (index) { - widget.removeChild(items[0]); - addItem(completions[index + CONTEXT_LINES], false); - } - else if (new_offset == list_offset - 1) - { - widget.removeChild(items[items.length-1]); - addItem(completions[index - CONTEXT_LINES], true); - } - else if (new_offset == list_offset) - { - // do nothing - } - else - fill(new_offset); + if (widget.hidden) + return; - list_offset = new_offset; - widget.selectedIndex = index - list_offset; - } + if (!incremental_fill) + { + widget.selectedIndex = index; + return; + } - this.onEvent = function (event) - { - var listcells = document.getElementsByTagName("listcell"); - // 2 columns for now, use the first column - var index = (widget.selectedIndex * 2) + 0; - var val = listcells[index].getAttribute("label"); - if (val && event.button == 0 && event.type == "dblclick") // left double click - vimperator.open(val); - else if (val && event.button == 1) // middle click - vimperator.open(val, vimperator.NEW_TAB); - else - return false; - } + // find start index + var new_offset = 0; + if (index >= list_offset + max_items - CONTEXT_LINES) + new_offset = index - max_items + CONTEXT_LINES + 1; + else if (index <= list_offset + CONTEXT_LINES) + new_offset = index - CONTEXT_LINES; + else + new_offset = list_offset; + + if (new_offset + max_items > completions.length) + new_offset = completions.length - max_items; + if (new_offset < 0) + new_offset = 0; + + // for speed reason: just remove old item, and add the new one at the end of the list + var items = widget.getElementsByTagName("listitem"); + if (new_offset == list_offset + 1) + { + widget.removeChild(items[0]); + addItem(completions[index + CONTEXT_LINES], false); + } + else if (new_offset == list_offset - 1) + { + widget.removeChild(items[items.length-1]); + addItem(completions[index - CONTEXT_LINES], true); + } + else if (new_offset == list_offset) + { + // do nothing + } + else + fill(new_offset); + + list_offset = new_offset; + widget.selectedIndex = index - list_offset; + }, + + onEvent: function (event) + { + var listcells = document.getElementsByTagName("listcell"); + // 2 columns for now, use the first column + var index = (widget.selectedIndex * 2) + 0; + var val = listcells[index].getAttribute("label"); + if (val && event.button == 0 && event.type == "dblclick") // left double click + vimperator.open(val); + else if (val && event.button == 1) // middle click + vimperator.open(val, vimperator.NEW_TAB); + else + return false; + } + + }; //}}} } //}}} @@ -1065,138 +1073,142 @@ vimperator.StatusLine = function () //{{{ ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - this.setClass = function (type) - { - var highlight_group; + return { - switch (type) + setClass: function (type) { - case "secure": - highlight_group = "hl-StatusLineSecure"; - break; - case "broken": - highlight_group = "hl-StatusLineBroken"; - break; - case "insecure": - highlight_group = "hl-StatusLine"; - break; - } + var highlight_group; - status_bar.setAttribute("class", "chromeclass-status " + highlight_group); - }; - - // update all fields of the statusline - this.update = function () - { - this.updateUrl(); - this.updateInputBuffer(); - this.updateProgress(); - this.updateTabCount(); - this.updateBufferPosition(); - } - - // if "url" is ommited, build a usable string for the URL - this.updateUrl = function (url) - { - if (typeof url == "string") - { - url_widget.value = url; - return; - } - - url = vimperator.buffer.URL; - - // make it even more vim-like - if (url == "about:blank") - { - var title = vimperator.buffer.title; - - if (title == "Vimperator Help") - url = "[Help]"; - else if (!title) - url = "[No Name]"; - } - - var sh = getWebNavigation().sessionHistory; - var modified = ""; - if (sh.index > 0) - modified += "+"; - if (sh.index < sh.count -1) - modified += "-"; - - if (modified) - url += " [" + modified + "]" - - url_widget.value = url; - }; - - this.updateInputBuffer = function (buffer) - { - if (!buffer || typeof buffer != "string") - buffer = ""; - - inputbuffer_widget.value = buffer; - }; - - this.updateProgress = function (progress) - { - if (!progress) - progress = ""; - - if (typeof progress == "string") - progress_widget.value = progress; - else if (typeof progress == "number") - { - var progress_str = ""; - if (progress <= 0) - progress_str = "[ Loading... ]"; - else if (progress < 1) + switch (type) { - progress_str = "["; - var done = Math.floor(progress * 20); - for (var i = 0; i < done; i++) - progress_str += "="; - - progress_str += ">"; - - for (var i = 19; i > done; i--) - progress_str += " "; - - progress_str += "]"; + case "secure": + highlight_group = "hl-StatusLineSecure"; + break; + case "broken": + highlight_group = "hl-StatusLineBroken"; + break; + case "insecure": + highlight_group = "hl-StatusLine"; + break; } - progress_widget.value = progress_str; - } - }; - // you can omit either of the 2 arguments - this.updateTabCount = function (cur_index, total_tabs) - { - if (!cur_index || typeof cur_index != "number") - cur_index = vimperator.tabs.index() + 1; - if (!total_tabs || typeof cur_index != "number") - total_tabs = vimperator.tabs.count(); + status_bar.setAttribute("class", "chromeclass-status " + highlight_group); + }, - tabcount_widget.value = "[" + cur_index + "/" + total_tabs + "]"; - }; - - // percent is given between 0 and 1 - this.updateBufferPosition = function (percent) - { - if (!percent || typeof percent != "number") + // update all fields of the statusline + update: function () { - var win = document.commandDispatcher.focusedWindow; - percent = win.scrollMaxY == 0 ? -1 : win.scrollY / win.scrollMaxY; + this.updateUrl(); + this.updateInputBuffer(); + this.updateProgress(); + this.updateTabCount(); + this.updateBufferPosition(); + }, + + // if "url" is ommited, build a usable string for the URL + updateUrl: function (url) + { + if (typeof url == "string") + { + url_widget.value = url; + return; + } + + url = vimperator.buffer.URL; + + // make it even more vim-like + if (url == "about:blank") + { + var title = vimperator.buffer.title; + + if (title == "Vimperator Help") + url = "[Help]"; + else if (!title) + url = "[No Name]"; + } + + var sh = getWebNavigation().sessionHistory; + var modified = ""; + if (sh.index > 0) + modified += "+"; + if (sh.index < sh.count -1) + modified += "-"; + + if (modified) + url += " [" + modified + "]" + + url_widget.value = url; + }, + + updateInputBuffer: function (buffer) + { + if (!buffer || typeof buffer != "string") + buffer = ""; + + inputbuffer_widget.value = buffer; + }, + + updateProgress: function (progress) + { + if (!progress) + progress = ""; + + if (typeof progress == "string") + progress_widget.value = progress; + else if (typeof progress == "number") + { + var progress_str = ""; + if (progress <= 0) + progress_str = "[ Loading... ]"; + else if (progress < 1) + { + progress_str = "["; + var done = Math.floor(progress * 20); + for (var i = 0; i < done; i++) + progress_str += "="; + + progress_str += ">"; + + for (var i = 19; i > done; i--) + progress_str += " "; + + progress_str += "]"; + } + progress_widget.value = progress_str; + } + }, + + // you can omit either of the 2 arguments + updateTabCount: function (cur_index, total_tabs) + { + if (!cur_index || typeof cur_index != "number") + cur_index = vimperator.tabs.index() + 1; + if (!total_tabs || typeof cur_index != "number") + total_tabs = vimperator.tabs.count(); + + tabcount_widget.value = "[" + cur_index + "/" + total_tabs + "]"; + }, + + // percent is given between 0 and 1 + updateBufferPosition: function (percent) + { + if (!percent || typeof percent != "number") + { + var win = document.commandDispatcher.focusedWindow; + percent = win.scrollMaxY == 0 ? -1 : win.scrollY / win.scrollMaxY; + } + + var bufferposition_str = ""; + percent = Math.round(percent * 100); + if (percent < 0) bufferposition_str = "All"; + else if (percent == 0) bufferposition_str = "Top"; + else if (percent < 10) bufferposition_str = " " + percent + "%"; + else if (percent >= 100) bufferposition_str = "Bot"; + else bufferposition_str = percent + "%"; + + bufferposition_widget.value = bufferposition_str; } - var bufferposition_str = ""; - percent = Math.round(percent * 100); - if (percent < 0) bufferposition_str = "All"; - else if (percent == 0) bufferposition_str = "Top"; - else if (percent < 10) bufferposition_str = " " + percent + "%"; - else if (percent >= 100) bufferposition_str = "Bot"; - else bufferposition_str = percent + "%"; - - bufferposition_widget.value = bufferposition_str; }; //}}} } //}}} diff --git a/content/vimperator.js b/content/vimperator.js index 1b5b1452..9aa061eb 100644 --- a/content/vimperator.js +++ b/content/vimperator.js @@ -584,37 +584,37 @@ const vimperator = (function () //{{{ vimperator.log("Loading module commands...", 3); vimperator.commands = new vimperator.Commands(); vimperator.log("Loading module bookmarks...", 3); - vimperator.bookmarks = new vimperator.Bookmarks(); + vimperator.bookmarks = vimperator.Bookmarks(); vimperator.log("Loading module history...", 3); - vimperator.history = new vimperator.History(); + vimperator.history = vimperator.History(); vimperator.log("Loading module commandline...", 3); - vimperator.commandline = new vimperator.CommandLine(); + vimperator.commandline = vimperator.CommandLine(); vimperator.log("Loading module search...", 3); - vimperator.search = new vimperator.Search(); + vimperator.search = vimperator.Search(); vimperator.log("Loading module preview window...", 3); - vimperator.previewwindow = new vimperator.InformationList("vimperator-previewwindow", { incremental_fill: false, max_items: 10 }); + vimperator.previewwindow = vimperator.InformationList("vimperator-previewwindow", { incremental_fill: false, max_items: 10 }); vimperator.log("Loading module buffer window...", 3); - vimperator.bufferwindow = new vimperator.InformationList("vimperator-bufferwindow", { incremental_fill: false, max_items: 10 }); + vimperator.bufferwindow = vimperator.InformationList("vimperator-bufferwindow", { incremental_fill: false, max_items: 10 }); vimperator.log("Loading module mappings...", 3); vimperator.mappings = new vimperator.Mappings(); vimperator.log("Loading module statusline...", 3); - vimperator.statusline = new vimperator.StatusLine(); + vimperator.statusline = vimperator.StatusLine(); vimperator.log("Loading module buffer...", 3); - vimperator.buffer = new vimperator.Buffer(); + vimperator.buffer = vimperator.Buffer(); vimperator.log("Loading module editor...", 3); - vimperator.editor = new vimperator.Editor(); + vimperator.editor = vimperator.Editor(); vimperator.log("Loading module tabs...", 3); vimperator.tabs = new vimperator.Tabs(); vimperator.log("Loading module marks...", 3); - vimperator.marks = new vimperator.Marks(); + vimperator.marks = vimperator.Marks(); vimperator.log("Loading module quickmarks...", 3); - vimperator.quickmarks = new vimperator.QuickMarks(); + vimperator.quickmarks = vimperator.QuickMarks(); vimperator.log("Loading module hints...", 3); vimperator.hints = new vimperator.Hints(); vimperator.log("Loading module io...", 3); - vimperator.io = new vimperator.IO(); + vimperator.io = vimperator.IO(); vimperator.log("Loading module completion...", 3); - vimperator.completion = new vimperator.Completion(); + vimperator.completion = vimperator.Completion(); vimperator.log("All modules loaded", 3); vimperator.echo = function (str, flags) { vimperator.commandline.echo(str, vimperator.commandline.HL_NORMAL, flags); };