mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2026-02-14 06:45:51 +01:00
change directory structure to follow the more standard package hierarchy
This commit is contained in:
868
content/bookmarks.js
Normal file
868
content/bookmarks.js
Normal file
@@ -0,0 +1,868 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
// also includes methods for dealing with keywords and search engines
|
||||
function Bookmarks() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// 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"]
|
||||
.getService(Components.interfaces.nsINavBookmarksService);
|
||||
const search_service = Components.classes["@mozilla.org/browser/search-service;1"].
|
||||
getService(Components.interfaces.nsIBrowserSearchService);
|
||||
const io_service = Components.classes['@mozilla.org/network/io-service;1']
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
|
||||
var bookmarks = null;
|
||||
var keywords = null;
|
||||
|
||||
if (vimperator.options["preload"])
|
||||
setTimeout(function() { load(); } , 100);
|
||||
|
||||
function load()
|
||||
{
|
||||
// update our bookmark cache
|
||||
bookmarks = []; // also clear our bookmark cache
|
||||
keywords = [];
|
||||
var root = bookmarks_service.bookmarksRoot;
|
||||
|
||||
var folders = [root];
|
||||
var query = history_service.getNewQuery();
|
||||
var options = history_service.getNewQueryOptions();
|
||||
// query.searchTerms = "test";
|
||||
while (folders.length > 0)
|
||||
{
|
||||
//comment out the next line for now; the bug hasn't been fixed; final version should include the next line
|
||||
//options.setGroupingMode(options.GROUP_BY_FOLDER);
|
||||
query.setFolders(folders, 1);
|
||||
var result = history_service.executeQuery(query, options);
|
||||
result.sortingMode = options.SORT_BY_DATE_DESCENDING;
|
||||
//result.sortingMode = options.SORT_BY_VISITCOUNT_DESCENDING;
|
||||
var rootNode = result.root;
|
||||
rootNode.containerOpen = true;
|
||||
|
||||
folders.shift();
|
||||
// iterate over the immediate children of this folder
|
||||
for (var i = 0; i < rootNode.childCount; i++)
|
||||
{
|
||||
var node = rootNode.getChild(i);
|
||||
if (node.type == node.RESULT_TYPE_FOLDER) // folder
|
||||
folders.push(node.itemId);
|
||||
else if (node.type == node.RESULT_TYPE_URI) // bookmark
|
||||
{
|
||||
bookmarks.push([node.uri, node.title]);
|
||||
var kw = bookmarks_service.getKeywordForBookmark(node.itemId);
|
||||
if (kw)
|
||||
keywords.push([kw, node.title, node.uri]);
|
||||
}
|
||||
}
|
||||
|
||||
// close a container after using it!
|
||||
rootNode.containerOpen = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// FIXME: add filtering here rather than having to calling
|
||||
// get_bookmark_completions()
|
||||
this.get = function()
|
||||
{
|
||||
if (!bookmarks)
|
||||
load();
|
||||
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
this.add = function (title, url, keyword)
|
||||
{
|
||||
if (!bookmarks)
|
||||
load();
|
||||
|
||||
var uri = io_service.newURI(url, null, null);
|
||||
var id = bookmarks_service.insertBookmark(bookmarks_service.bookmarksRoot, uri, -1, title);
|
||||
if (id && keyword)
|
||||
{
|
||||
bookmarks_service.setKeywordForBookmark(id, keyword);
|
||||
keywords.unshift([keyword, title, url]);
|
||||
}
|
||||
|
||||
//also update bookmark cache
|
||||
bookmarks.unshift([url, title]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns number of deleted bookmarks
|
||||
this.remove = function(url)
|
||||
{
|
||||
if (!url)
|
||||
return 0;
|
||||
|
||||
var uri = io_service.newURI(url, null, null);
|
||||
var count = {};
|
||||
var bmarks = bookmarks_service.getBookmarkIdsForURI(uri, count);
|
||||
|
||||
for (var i = 0; i < bmarks.length; i++)
|
||||
bookmarks_service.removeItem(bmarks[i]);
|
||||
|
||||
// also update bookmark cache, if we removed at least one bookmark
|
||||
if (count.value > 0)
|
||||
load();
|
||||
|
||||
return count.value;
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 == "")
|
||||
engine_name = vimperator.options["defsearch"];
|
||||
|
||||
// we need to make sure our custom alias have been set, even if the user
|
||||
// did not :open <tab> once before
|
||||
this.getSearchEngines();
|
||||
|
||||
// first checks the search engines for a match
|
||||
var engine = search_service.getEngineByAlias(engine_name);
|
||||
if (engine)
|
||||
{
|
||||
if (text)
|
||||
{
|
||||
var submission = engine.getSubmission(text, null);
|
||||
url = submission.uri.spec;
|
||||
postdata = submission.postData;
|
||||
}
|
||||
else
|
||||
url = engine.searchForm;
|
||||
}
|
||||
else // check for keyword urls
|
||||
{
|
||||
if (!keywords)
|
||||
load();
|
||||
|
||||
for (var i in keywords)
|
||||
{
|
||||
if (keywords[i][0] == engine_name)
|
||||
{
|
||||
if (text == null)
|
||||
text = "";
|
||||
url = keywords[i][2].replace(/%s/g, encodeURIComponent(text));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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, fullmode)
|
||||
{
|
||||
if (fullmode)
|
||||
{
|
||||
vimperator.open("chrome://browser/content/bookmarks/bookmarksPanel.xul", vimperator.NEW_TAB);
|
||||
}
|
||||
else
|
||||
{
|
||||
var items = vimperator.completion.get_bookmark_completions(filter);
|
||||
|
||||
if (items.length == 0)
|
||||
{
|
||||
if (filter.length > 0)
|
||||
vimperator.echoerr("E283: No bookmarks matching \"" + filter + "\"");
|
||||
else
|
||||
vimperator.echoerr("No bookmarks set");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.length; i++)
|
||||
{
|
||||
var list = "<table style=\"white-space: nowrap;\"><tr align=\"left\" style=\"color: magenta\"><th>title</th><th>keyword</th><th>URL</th><th align=\"right\">tags</th></tr>";
|
||||
for (var i = 0; i < items.length; i++)
|
||||
{
|
||||
var title = items[i][1].replace(/</, "<").replace(/>/, ">");
|
||||
if (title.length > 50)
|
||||
title = title.substr(0, 47) + "...";
|
||||
var keyword = "".substr(0,12); // maximum 12 chars
|
||||
var url = items[i][0].replace(/</, "<").replace(/>/, ">");
|
||||
var tags = "tag1, tag2";
|
||||
list += "<tr><td>" + title + "</td><td style=\"color: blue\" align=\"center\">" + keyword +
|
||||
"</td><td style=\"color: green; width: 100%\">" + url +
|
||||
"</td><td style=\"color: red;\" align=\"right\">" + tags + "</td></tr>";
|
||||
// TODO: change that list to something like this when we have keywords
|
||||
//list += "<tr><td width=\"30%\"><span style=\"font-weight: bold\">" + items[i][1].substr(0,20) + "</span></td><td width=\"70%\"><span style=\"color: green\">" + items[i][0] + "</span><br/>" + "Keyword: <span style=\"color: blue\">foo</span> Tags: <span style=\"color: red\">computer, news</span>" + "</td></tr>";
|
||||
|
||||
}
|
||||
list += "</table>";
|
||||
|
||||
vimperator.commandline.echo(list, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// res = parseBookmarkString("-t tag1,tag2 -T title http://www.orf.at");
|
||||
// res.tags is an array of tags
|
||||
// res.title is the title or "" if no one was given
|
||||
// res.url is the url as a string
|
||||
// returns null, if parsing failed
|
||||
Bookmarks.parseBookmarkString = function(str)
|
||||
{
|
||||
var res = {};
|
||||
res.tags = [];
|
||||
res.title = null;
|
||||
res.url = null;
|
||||
|
||||
var re_title = /^\s*((-t|--title)\s+(\w+|\".*\"))(.*)/;
|
||||
var re_tags = /^\s*((-T|--tags)\s+((\w+)(,\w+)*))(.*)/;
|
||||
var re_url = /^\s*(\".+\"|\S+)(.*)/;
|
||||
|
||||
var match_tags = null;
|
||||
var match_title = null;
|
||||
var match_url = null;
|
||||
|
||||
while (!str.match(/^\s*$/))
|
||||
{
|
||||
// first check for --tags
|
||||
match_tags = str.match(re_tags);
|
||||
if (match_tags != null)
|
||||
{
|
||||
str = match_tags[match_tags.length - 1]; // the last captured parenthesis is the rest of the string
|
||||
tags = match_tags[3].split(",");
|
||||
res.tags = res.tags.concat(tags);
|
||||
}
|
||||
else // then for --titles
|
||||
{
|
||||
|
||||
match_title = str.match(re_title);
|
||||
if (match_title != null)
|
||||
{
|
||||
// only one title allowed
|
||||
if (res.title != null)
|
||||
return null;
|
||||
|
||||
str = match_title[match_title.length - 1]; // the last captured parenthesis is the rest of the string
|
||||
var title = match_title[3];
|
||||
if (title.charAt(0) == '"')
|
||||
title = title.substring(1, title.length - 1);
|
||||
res.title = title;
|
||||
}
|
||||
else // at last check for a URL
|
||||
{
|
||||
match_url = str.match(re_url);
|
||||
if (match_url != null)
|
||||
{
|
||||
// only one url allowed
|
||||
if (res.url != null)
|
||||
return null;
|
||||
|
||||
str = match_url[match_url.length - 1]; // the last captured parenthesis is the rest of the string
|
||||
url = match_url[1];
|
||||
if (url.charAt(0) == '"')
|
||||
url = url.substring(1, url.length - 1);
|
||||
res.url = url;
|
||||
}
|
||||
else
|
||||
return null; // no url, tag or title found but still text left, abort
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
function History() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
const history_service = Components.classes["@mozilla.org/browser/nav-history-service;1"]
|
||||
.getService(Components.interfaces.nsINavHistoryService);
|
||||
|
||||
var history = null;
|
||||
|
||||
if (vimperator.options["preload"])
|
||||
setTimeout(function() { load(); } , 100);
|
||||
|
||||
function load()
|
||||
{
|
||||
history = [];
|
||||
|
||||
// no query parameters will get all history
|
||||
// XXX default sorting is... ?
|
||||
var options = history_service.getNewQueryOptions();
|
||||
var query = history_service.getNewQuery();
|
||||
|
||||
// execute the query
|
||||
var result = history_service.executeQuery(query, options);
|
||||
var rootNode = result.root;
|
||||
rootNode.containerOpen = true;
|
||||
// iterate over the immediate children of this folder
|
||||
for (var i = 0; i < rootNode.childCount; i++)
|
||||
{
|
||||
var node = rootNode.getChild(i);
|
||||
if (node.type == node.RESULT_TYPE_URI) // just make sure it's a bookmark
|
||||
{
|
||||
history.push([node.uri, node.title]);
|
||||
}
|
||||
else
|
||||
dump("History child " + node.itemId + ": " + node.title + " - " + node.type + "\n");
|
||||
}
|
||||
|
||||
// close a container after using it!
|
||||
rootNode.containerOpen = false;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// FIXME: add filtering here rather than having to call
|
||||
// get_bookmark_completions()
|
||||
this.get = function()
|
||||
{
|
||||
if (!history)
|
||||
load();
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
getWebNavigation().gotoIndex(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
vimperator.beep();
|
||||
}
|
||||
}
|
||||
|
||||
this.goToStart = function()
|
||||
{
|
||||
var index = getWebNavigation().sessionHistory.index;
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
vimperator.beep();
|
||||
return;
|
||||
}
|
||||
|
||||
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 = vimperator.completion.get_history_completions(filter);
|
||||
|
||||
if (items.length == 0)
|
||||
{
|
||||
if (filter.length > 0)
|
||||
vimperator.echoerr("E283: No history matching \"" + filter + "\"");
|
||||
else
|
||||
vimperator.echoerr("No history set");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.length; i++)
|
||||
{
|
||||
var list = "<table style=\"white-space: nowrap;\"><tr align=\"left\" style=\"color: magenta\"><th>title</th><th>URL</th></tr>";
|
||||
for (var i = 0; i < items.length; i++)
|
||||
{
|
||||
var title = items[i][1].replace(/</, "<").replace(/>/, ">");
|
||||
if (title.length > 50)
|
||||
title = title.substr(0, 47) + "...";
|
||||
var url = items[i][0].replace(/</, "<").replace(/>/, ">");
|
||||
list += "<tr><td>" + title + "</td><td style=\"color: green;\">" + url + "</td></tr>";
|
||||
}
|
||||
list += "</table>";
|
||||
|
||||
vimperator.commandline.echo(list, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
function Marks() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var local_marks = {};
|
||||
var url_marks = {};
|
||||
var pending_jumps = [];
|
||||
var appcontent = document.getElementById("appcontent");
|
||||
|
||||
if (appcontent)
|
||||
appcontent.addEventListener("load", onPageLoad, true);
|
||||
|
||||
function onPageLoad(event)
|
||||
{
|
||||
var win = event.originalTarget.defaultView;
|
||||
for (var i = 0, length = pending_jumps.length; i < length; i++)
|
||||
{
|
||||
var mark = pending_jumps[i];
|
||||
if (win.location.href == mark.location)
|
||||
{
|
||||
win.scrollTo(mark.position.x * win.scrollMaxX, mark.position.y * win.scrollMaxY);
|
||||
pending_jumps.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeLocalMark(mark)
|
||||
{
|
||||
if (mark in local_marks)
|
||||
{
|
||||
var win = window.content;
|
||||
for (var i = 0; i < local_marks[mark].length; i++)
|
||||
{
|
||||
if (local_marks[mark][i].location == win.location.href)
|
||||
{
|
||||
vimperator.log("Deleting local mark: " + mark + " | " + local_marks[mark][i].location + " | (" + local_marks[mark][i].position.x + ", " + local_marks[mark][i].position.y + ") | tab: " + vimperator.tabs.index(local_marks[mark][i].tab), 5);
|
||||
local_marks[mark].splice(i, 1);
|
||||
if (local_marks[mark].length == 0)
|
||||
delete local_marks[mark];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeURLMark(mark)
|
||||
{
|
||||
if (mark in url_marks)
|
||||
{
|
||||
vimperator.log("Deleting URL mark: " + mark + " | " + url_marks[mark].location + " | (" + url_marks[mark].position.x + ", " + url_marks[mark].position.y + ") | tab: " + vimperator.tabs.index(url_marks[mark].tab), 5);
|
||||
delete url_marks[mark];
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalMark(mark)
|
||||
{
|
||||
return /^[a-z]$/.test(mark);
|
||||
}
|
||||
|
||||
function isURLMark(mark)
|
||||
{
|
||||
return /^[A-Z0-9]$/.test(mark);
|
||||
}
|
||||
|
||||
function getSortedMarks()
|
||||
{
|
||||
// local marks
|
||||
var lmarks = [];
|
||||
|
||||
for (var mark in local_marks)
|
||||
{
|
||||
for (var i = 0; i < local_marks[mark].length; i++)
|
||||
{
|
||||
if (local_marks[mark][i].location == window.content.location.href)
|
||||
lmarks.push([mark, local_marks[mark][i]]);
|
||||
}
|
||||
}
|
||||
lmarks.sort();
|
||||
|
||||
// URL marks
|
||||
var umarks = [];
|
||||
|
||||
for (var mark in url_marks)
|
||||
umarks.push([mark, url_marks[mark]]);
|
||||
// FIXME: why does umarks.sort() cause a "Component is not available =
|
||||
// NS_ERROR_NOT_AVAILABLE" exception when used here?
|
||||
umarks.sort(function(a, b) {
|
||||
if (a[0] < b[0])
|
||||
return -1;
|
||||
else if (a[0] > b[0])
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
});
|
||||
|
||||
return lmarks.concat(umarks);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// TODO: add support for frameset pages
|
||||
this.add = function(mark)
|
||||
{
|
||||
var win = window.content;
|
||||
|
||||
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))
|
||||
{
|
||||
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?
|
||||
}
|
||||
|
||||
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 + "\"");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var list = "<table><tr align=\"left\" style=\"color: magenta\"><th>mark</th><th>line</th><th>col</th><th>file</th></tr>";
|
||||
for (var i = 0; i < marks.length; i++)
|
||||
{
|
||||
list += "<tr>"
|
||||
+ "<td> " + marks[i][0] + "</td>"
|
||||
+ "<td align=\"right\">" + Math.round(marks[i][1].position.y * 100) + "%</td>"
|
||||
+ "<td align=\"right\">" + Math.round(marks[i][1].position.x * 100) + "%</td>"
|
||||
+ "<td style=\"color: green;\">" + marks[i][1].location.replace(/</, "<").replace(/>/, ">") + "</td>"
|
||||
+ "</tr>";
|
||||
}
|
||||
list += "</table>";
|
||||
|
||||
vimperator.commandline.echo(list, true); // TODO: force of multiline widget a better way
|
||||
}
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
function QuickMarks() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var qmarks = {};
|
||||
var saved_marks = Options.getPref("quickmarks", "").split("\n");
|
||||
|
||||
// load the saved quickmarks -- TODO: change to sqlite
|
||||
for (var i = 0; i < saved_marks.length - 1; i += 2)
|
||||
{
|
||||
qmarks[saved_marks[i]] = saved_marks[i + 1];
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
this.add = function(qmark, location)
|
||||
{
|
||||
qmarks[qmark] = location;
|
||||
}
|
||||
|
||||
this.remove = function(filter)
|
||||
{
|
||||
var pattern = new RegExp("[" + filter.replace(/\s+/g, '') + "]");
|
||||
|
||||
for (var qmark in qmarks)
|
||||
{
|
||||
if (pattern.test(qmark))
|
||||
delete qmarks[qmark];
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
vimperator.echoerr("No QuickMarks 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 QuickMarks matching \"" + filter + "\"");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var list = "<table><tr align=\"left\" style=\"color: magenta\"><th>QuickMark</th><th>URL</th></tr>";
|
||||
for (var i = 0; i < marks.length; i++)
|
||||
{
|
||||
list += "<tr><td> " + marks[i][0] +
|
||||
"</td><td style=\"color: green;\">" + marks[i][1].replace(/</, "<").replace(/>/, ">") + "</td></tr>";
|
||||
}
|
||||
list += "</table>";
|
||||
|
||||
vimperator.commandline.echo(list, true); // TODO: force of multiline widget a better way
|
||||
}
|
||||
|
||||
this.destroy = function()
|
||||
{
|
||||
// save the quickmarks
|
||||
var saved_qmarks = "";
|
||||
|
||||
for (var i in qmarks)
|
||||
{
|
||||
saved_qmarks += i + "\n";
|
||||
saved_qmarks += qmarks[i] + "\n";
|
||||
}
|
||||
|
||||
Options.setPref("quickmarks", saved_qmarks);
|
||||
}
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
435
content/buffers.js
Normal file
435
content/buffers.js
Normal file
@@ -0,0 +1,435 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
function Buffer() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var zoom_levels = [ 1, 10, 25, 50, 75, 90, 100,
|
||||
120, 150, 200, 300, 500, 1000, 2000 ];
|
||||
|
||||
function setZoom(value, full_zoom)
|
||||
{
|
||||
if (value < 1 || value > 2000)
|
||||
{
|
||||
vimperator.echoerr("Zoom value out of range (1-2000%)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (full_zoom)
|
||||
getBrowser().mCurrentBrowser.markupDocumentViewer.fullZoom = value / 100.0;
|
||||
else
|
||||
getBrowser().mCurrentBrowser.markupDocumentViewer.textZoom = value / 100.0;
|
||||
|
||||
vimperator.echo((full_zoom ? "Full zoom: " : "Text zoom: ") + value + "%");
|
||||
|
||||
// TODO: shouldn't this just recalculate hint coords, rather than
|
||||
// unsuccessfully attempt to reshow hints? i.e. isn't it just relying
|
||||
// on the recalculation side effect? -- djk
|
||||
// NOTE: we could really do with a zoom event...
|
||||
vimperator.hints.reshowHints();
|
||||
}
|
||||
|
||||
function bumpZoomLevel(steps, full_zoom)
|
||||
{
|
||||
if (full_zoom)
|
||||
var value = getBrowser().mCurrentBrowser.markupDocumentViewer.fullZoom * 100.0;
|
||||
else
|
||||
var value = getBrowser().mCurrentBrowser.markupDocumentViewer.textZoom * 100.0;
|
||||
|
||||
var index = -1;
|
||||
if (steps <= 0)
|
||||
{
|
||||
for (var i = zoom_levels.length - 1; i >= 0; i--)
|
||||
{
|
||||
if ((zoom_levels[i] + 0.01) < value) // 0.01 for float comparison
|
||||
{
|
||||
index = i + 1 + steps;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < zoom_levels.length; i++)
|
||||
{
|
||||
if ((zoom_levels[i] - 0.01) > value) // 0.01 for float comparison
|
||||
{
|
||||
index = i - 1 + steps;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index < 0 || index >= zoom_levels.length)
|
||||
{
|
||||
vimperator.beep();
|
||||
return;
|
||||
}
|
||||
setZoom(zoom_levels[index], full_zoom);
|
||||
}
|
||||
|
||||
function checkScrollYBounds(win, direction)
|
||||
{
|
||||
// NOTE: it's possible to have scrollY > scrollMaxY - FF bug?
|
||||
if (direction > 0 && win.scrollY >= win.scrollMaxY || direction < 0 && win.scrollY == 0)
|
||||
vimperator.beep();
|
||||
}
|
||||
|
||||
// both values are given in percent, -1 means no change
|
||||
function scrollToPercentiles(horizontal, vertical)
|
||||
{
|
||||
var win = document.commandDispatcher.focusedWindow;
|
||||
var h, v;
|
||||
|
||||
if (horizontal < 0)
|
||||
h = win.scrollX;
|
||||
else
|
||||
h = win.scrollMaxX / 100 * horizontal;
|
||||
|
||||
if (vertical < 0)
|
||||
v = win.scrollY;
|
||||
else
|
||||
v = win.scrollMaxY / 100 * vertical;
|
||||
|
||||
win.scrollTo(h, v);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
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 getBrowser().mPanelContainer.boxObject.height; // FIXME: best way to do this?
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
this.lastInputField = null; // used to keep track of the right field for "gi"
|
||||
|
||||
// returns an XPathResult object
|
||||
this.evaluateXPath = function(expression, doc, elem, ordered)
|
||||
{
|
||||
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;
|
||||
}
|
||||
},
|
||||
ordered ? XPathResult.ORDERED_NODE_SNAPSHOT_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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
// TODO: move to v.buffers.list()
|
||||
this.list = function(fullmode)
|
||||
{
|
||||
if (fullmode)
|
||||
{
|
||||
// toggle the special buffer previw window
|
||||
if (vimperator.bufferwindow.visible())
|
||||
{
|
||||
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 = "<table style=\"white-space: pre;\">"
|
||||
for (var i = 0; i < items.length; i++)
|
||||
{
|
||||
if (i == vimperator.tabs.index())
|
||||
indicator = " <span style=\"color: blue\">%</span> ";
|
||||
else if (i == vimperator.tabs.index(vimperator.tabs.alternate))
|
||||
indicator = " <span style=\"color: blue\">#</span> ";
|
||||
else
|
||||
indicator = " ";
|
||||
|
||||
[number, title] = items[i][0].split(/:\s+/, 2);
|
||||
url = items[i][1];
|
||||
url = url.replace(/>/, ">").replace(/</, "<");
|
||||
title = title.replace(/>/, ">").replace(/</, "<");
|
||||
|
||||
list += "<tr><td align=\"right\"> " + number + "</td><td>" + indicator +
|
||||
"</td><td style=\"width: 250px; max-width: 500px; overflow: hidden;\">" + title +
|
||||
"</td><td><span style=\"color: green\">" + url + "</span></td></tr>";
|
||||
}
|
||||
list += "</table>";
|
||||
|
||||
vimperator.commandline.echo(list, true);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
try
|
||||
{
|
||||
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
|
||||
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
|
||||
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);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// FIXME: fail silently here for now
|
||||
//vimperator.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
this.zoomIn = function(steps, full_zoom)
|
||||
{
|
||||
bumpZoomLevel(steps, full_zoom);
|
||||
}
|
||||
|
||||
this.zoomOut = function(steps, full_zoom)
|
||||
{
|
||||
bumpZoomLevel(-steps, full_zoom);
|
||||
}
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
1726
content/commands.js
Normal file
1726
content/commands.js
Normal file
File diff suppressed because it is too large
Load Diff
591
content/completion.js
Normal file
591
content/completion.js
Normal file
@@ -0,0 +1,591 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
vimperator.completion = (function() // {{{
|
||||
{
|
||||
// The completion substrings, used for showing the longest common match
|
||||
var g_substrings = [];
|
||||
|
||||
// function uses smartcase
|
||||
// list = [ [['com1', 'com2'], 'text'], [['com3', 'com4'], 'text'] ]
|
||||
function build_longest_common_substring(list, filter) //{{{
|
||||
{
|
||||
var filtered = [];
|
||||
var ignorecase = false;
|
||||
if (filter == filter.toLowerCase())
|
||||
ignorecase = true;
|
||||
|
||||
for (var i = 0; i < list.length; i++)
|
||||
{
|
||||
for (var j = 0; j < list[i][0].length; j++)
|
||||
{
|
||||
var item = list[i][0][j];
|
||||
if (ignorecase)
|
||||
item = item.toLowerCase();
|
||||
|
||||
if (item.indexOf(filter) == -1)
|
||||
continue;
|
||||
|
||||
if (g_substrings.length == 0)
|
||||
{
|
||||
var last_index = item.lastIndexOf(filter);
|
||||
var length = item.length;
|
||||
for (var k = item.indexOf(filter); k != -1 && k <= last_index; k = item.indexOf(filter, k + 1))
|
||||
{
|
||||
for (var l = k + filter.length; l <= length; l++)
|
||||
g_substrings.push(list[i][0][j].substring(k, l));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_substrings = g_substrings.filter(function($_) {
|
||||
return list[i][0][j].indexOf($_) >= 0;
|
||||
});
|
||||
}
|
||||
filtered.push([list[i][0][j], list[i][1]]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
} //}}}
|
||||
|
||||
/* this function is case sensitive and should be documented about input and output ;) */
|
||||
function build_longest_starting_substring(list, filter) //{{{
|
||||
{
|
||||
var filtered = [];
|
||||
for (var i = 0; i < list.length; i++)
|
||||
{
|
||||
for (var j = 0; j < list[i][0].length; j++)
|
||||
{
|
||||
if (list[i][0][j].indexOf(filter) != 0)
|
||||
continue;
|
||||
|
||||
if (g_substrings.length == 0)
|
||||
{
|
||||
var length = list[i][0][j].length;
|
||||
for (var k = filter.length; k <= length; k++)
|
||||
g_substrings.push(list[i][0][j].substring(0, k));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_substrings = g_substrings.filter(function($_) {
|
||||
return list[i][0][j].indexOf($_) == 0;
|
||||
});
|
||||
}
|
||||
filtered.push([list[i][0][j], list[i][1]]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
} //}}}
|
||||
|
||||
/* discard all entries in the 'urls' array, which don't match 'filter */
|
||||
function filter_url_array(urls, filter) //{{{
|
||||
{
|
||||
var filtered = [];
|
||||
// completions which don't match the url but just the description
|
||||
// list them add the end of the array
|
||||
var additional_completions = [];
|
||||
|
||||
if (!filter) return urls.map(function($_) {
|
||||
return [$_[0], $_[1]]
|
||||
});
|
||||
|
||||
var ignorecase = false;
|
||||
if (filter == filter.toLowerCase())
|
||||
ignorecase = true;
|
||||
|
||||
/*
|
||||
* Longest Common Subsequence
|
||||
* This shouldn't use build_longest_common_substring
|
||||
* for performance reasons, so as not to cycle through the urls twice
|
||||
*/
|
||||
for (var i = 0; i < urls.length; i++)
|
||||
{
|
||||
var url = urls[i][0] || "";
|
||||
var title = urls[i][1] || "";
|
||||
if (ignorecase)
|
||||
{
|
||||
url = url.toLowerCase();
|
||||
title = title.toLowerCase();
|
||||
}
|
||||
|
||||
if (url.indexOf(filter) == -1)
|
||||
{
|
||||
if (title.indexOf(filter) != -1)
|
||||
additional_completions.push([ urls[i][0], urls[i][1] ]);
|
||||
continue;
|
||||
}
|
||||
if (g_substrings.length == 0) // Build the substrings
|
||||
{
|
||||
var last_index = url.lastIndexOf(filter);
|
||||
var url_length = url.length;
|
||||
for (var k = url.indexOf(filter); k != -1 && k <= last_index; k = url.indexOf(filter, k + 1))
|
||||
{
|
||||
for (var l = k + filter.length; l <= url_length; l++)
|
||||
g_substrings.push(url.substring(k, l));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_substrings = g_substrings.filter(function($_) {
|
||||
return url.indexOf($_) >= 0;
|
||||
});
|
||||
}
|
||||
filtered.push([urls[i][0], urls[i][1]]);
|
||||
}
|
||||
|
||||
return filtered.concat(additional_completions);
|
||||
} //}}}
|
||||
|
||||
return {
|
||||
/*
|
||||
* returns the longest common substring
|
||||
* used for the 'longest' setting for wildmode
|
||||
*/
|
||||
get_longest_substring: function() //{{{
|
||||
{
|
||||
if (g_substrings.length == 0)
|
||||
return '';
|
||||
|
||||
var longest = g_substrings[0];
|
||||
for (var i = 1; i < g_substrings.length; i++)
|
||||
{
|
||||
if (g_substrings[i].length > longest.length)
|
||||
longest = g_substrings[i];
|
||||
}
|
||||
return longest;
|
||||
}, //}}}
|
||||
|
||||
/*
|
||||
* filter a list of urls
|
||||
*
|
||||
* may consist of searchengines, filenames, bookmarks and history,
|
||||
* depending on the 'complete' option
|
||||
* if the 'complete' argument is passed like "h", it temporarily overrides the complete option
|
||||
*/
|
||||
get_url_completions: function(filter, complete) //{{{
|
||||
{
|
||||
var completions = [];
|
||||
g_substrings = [];
|
||||
|
||||
var cpt = complete || vimperator.options["complete"];
|
||||
// join all completion arrays together
|
||||
for (var i = 0; i < cpt.length; i++)
|
||||
{
|
||||
if (cpt[i] == 's')
|
||||
completions = completions.concat(this.get_search_completions(filter));
|
||||
else if (cpt[i] == 'b')
|
||||
completions = completions.concat(this.get_bookmark_completions(filter));
|
||||
else if (cpt[i] == 'h')
|
||||
completions = completions.concat(this.get_history_completions(filter));
|
||||
else if (cpt[i] == 'f')
|
||||
completions = completions.concat(this.get_file_completions(filter, true));
|
||||
}
|
||||
|
||||
return completions;
|
||||
}, //}}}
|
||||
|
||||
get_search_completions: function(filter) //{{{
|
||||
{
|
||||
var engines = vimperator.bookmarks.getSearchEngines().concat(vimperator.bookmarks.getKeywords());
|
||||
|
||||
if (!filter) return engines.map(function(engine) {
|
||||
return [engine[0], engine[1]];
|
||||
});
|
||||
var mapped = engines.map(function(engine) {
|
||||
return [[engine[0]], engine[1]];
|
||||
});
|
||||
return build_longest_common_substring(mapped, filter);
|
||||
}, //}}}
|
||||
|
||||
get_history_completions: function(filter) //{{{
|
||||
{
|
||||
var items = vimperator.history.get();
|
||||
return filter_url_array(items, filter);
|
||||
}, //}}}
|
||||
|
||||
get_bookmark_completions: function(filter) //{{{
|
||||
{
|
||||
var bookmarks = vimperator.bookmarks.get();
|
||||
return filter_url_array(bookmarks, filter);
|
||||
}, //}}}
|
||||
|
||||
// TODO: support file:// and \ or / path separators on both platforms
|
||||
get_file_completions: function(filter) //{{{
|
||||
{
|
||||
// this is now also used as part of the url completion, so the
|
||||
// substrings shouldn't be cleared for that case
|
||||
if (!arguments[1])
|
||||
g_substrings = [];
|
||||
|
||||
var matches = filter.match(/^(.*[\/\\])(.*?)$/);
|
||||
var dir;
|
||||
|
||||
if (!matches || !(dir = matches[1]))
|
||||
return [];
|
||||
|
||||
var compl = matches[2] || "";
|
||||
|
||||
try
|
||||
{
|
||||
var fd = vimperator.fopen(dir);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// thrown if file does not exist
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!fd)
|
||||
return [];
|
||||
|
||||
try
|
||||
{
|
||||
var entries = fd.read();
|
||||
var separator = fd.path.length == 1 ? "" : /\\/.test(fd.path) ? "\\" : "/";
|
||||
var new_filter = fd.path + separator + compl;
|
||||
if (!filter) return entries.map(function(file) {
|
||||
var path = file.path;
|
||||
if (file.isDirectory())
|
||||
path += separator;
|
||||
return [path, ""];
|
||||
});
|
||||
var mapped = entries.map(function(file) {
|
||||
var path = file.path;
|
||||
if (file.isDirectory())
|
||||
path += separator;
|
||||
return [[path], ""];
|
||||
});
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return build_longest_starting_substring(mapped, new_filter);
|
||||
}, //}}}
|
||||
|
||||
get_help_completions: function(filter) //{{{
|
||||
{
|
||||
var help_array = [[["introduction"], "Introductory text"],
|
||||
[["initialization"], "Initialization and startup"],
|
||||
[["mappings"], "Normal mode commands"],
|
||||
[["commands"], "Ex commands"],
|
||||
[["options"], "Configuration options"]]; // TODO: hardcoded until we have proper 'pages'
|
||||
g_substrings = [];
|
||||
for (var command in vimperator.commands)
|
||||
{
|
||||
help_array.push([command.long_names.map(function($_) {
|
||||
return ":" + $_;
|
||||
}),
|
||||
command.short_help])
|
||||
}
|
||||
options = this.get_options_completions(filter, true);
|
||||
help_array = help_array.concat(options.map(function($_) {
|
||||
return [
|
||||
$_[0].map(function($_) { return "'" + $_ + "'"; }),
|
||||
$_[1]
|
||||
];
|
||||
}));
|
||||
for (var map in vimperator.mappings)
|
||||
help_array.push([map.names, map.short_help])
|
||||
|
||||
if (!filter) return help_array.map(function($_) {
|
||||
return [$_[0][0], $_[1]]; // unfiltered, use the first command
|
||||
});
|
||||
|
||||
return build_longest_common_substring(help_array, filter);
|
||||
}, //}}}
|
||||
|
||||
get_command_completions: function(filter) //{{{
|
||||
{
|
||||
g_substrings = [];
|
||||
var completions = []
|
||||
if (!filter)
|
||||
{
|
||||
for (var command in vimperator.commands)
|
||||
completions.push([command.name, command.short_help]);
|
||||
return completions;
|
||||
}
|
||||
|
||||
for (var command in vimperator.commands)
|
||||
completions.push([command.long_names, command.short_help]);
|
||||
return build_longest_starting_substring(completions, filter);
|
||||
}, //}}}
|
||||
|
||||
get_options_completions: function(filter, unfiltered) //{{{
|
||||
{
|
||||
g_substrings = [];
|
||||
var options_completions = [];
|
||||
var prefix = filter.match(/^no|inv/) || "";
|
||||
|
||||
if (prefix)
|
||||
filter = filter.replace(prefix, "");
|
||||
|
||||
if (unfiltered)
|
||||
{
|
||||
var options = [];
|
||||
for (var option in vimperator.options)
|
||||
{
|
||||
if (prefix && option.type != "boolean")
|
||||
continue;
|
||||
options.push([option.names, option.short_help])
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
if (!filter)
|
||||
{
|
||||
var options = [];
|
||||
for (var option in vimperator.options)
|
||||
{
|
||||
if (prefix && option.type != "boolean")
|
||||
continue;
|
||||
options.push([prefix + option.name, option.short_help])
|
||||
}
|
||||
return options;
|
||||
}
|
||||
// check if filter ends with =, then complete current value
|
||||
else if (filter.length > 0 && filter.lastIndexOf("=") == filter.length - 1)
|
||||
{
|
||||
filter = filter.substr(0, filter.length - 1);
|
||||
for (var option in vimperator.options)
|
||||
{
|
||||
if (option.hasName(filter))
|
||||
{
|
||||
options_completions.push([filter + "=" + option.value, ""]);
|
||||
return options_completions;
|
||||
}
|
||||
}
|
||||
return options_completions;
|
||||
}
|
||||
|
||||
// can't use b_l_s_s, since this has special requirements (the prefix)
|
||||
var filter_length = filter.length;
|
||||
for (var option in vimperator.options)
|
||||
{
|
||||
if (prefix && option.type != "boolean")
|
||||
continue;
|
||||
|
||||
for (var j = 0; j < option.names.length; j++)
|
||||
{
|
||||
if (option.names[j].indexOf(filter) != 0)
|
||||
continue;
|
||||
|
||||
if (g_substrings.length == 0)
|
||||
{
|
||||
var length = option.names[j].length;
|
||||
for (var k = filter_length; k <= length; k++)
|
||||
g_substrings.push(prefix + option.names[j].substring(0, k));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_substrings = g_substrings.filter(function($_) {
|
||||
return option.names[j].indexOf($_) == 0;
|
||||
});
|
||||
}
|
||||
options_completions.push([prefix + option.names[j], option.short_help]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return options_completions;
|
||||
}, //}}}
|
||||
|
||||
get_buffer_completions: function(filter) //{{{
|
||||
{
|
||||
g_substrings = [];
|
||||
var items = [];
|
||||
var num = getBrowser().browsers.length;
|
||||
var title, url;
|
||||
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
title = getBrowser().getBrowserAtIndex(i).contentDocument.getElementsByTagName('title')[0].text;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
title = "";
|
||||
}
|
||||
|
||||
url = getBrowser().getBrowserAtIndex(i).contentDocument.location.href;
|
||||
|
||||
if (title.indexOf(filter) == -1 && url.indexOf(filter) == -1)
|
||||
continue;
|
||||
|
||||
if (title.indexOf(filter) != -1 || url.indexOf(filter) != -1)
|
||||
{
|
||||
if (title == "")
|
||||
title = "(Untitled)";
|
||||
items.push([[(i + 1) + ": " + title, (i + 1) + ": " + url], url]);
|
||||
}
|
||||
}
|
||||
if (!filter) return items.map(function($_) {
|
||||
return [$_[0][0], $_[1]];
|
||||
});
|
||||
return build_longest_common_substring(items, filter);
|
||||
}, //}}}
|
||||
|
||||
get_sidebar_completions: function(filter) //{{{
|
||||
{
|
||||
g_substrings = [];
|
||||
var menu = document.getElementById("viewSidebarMenu")
|
||||
var nodes = [];
|
||||
|
||||
for (var i = 0; i < menu.childNodes.length; i++)
|
||||
nodes.push([menu.childNodes[i].label, ""]);
|
||||
|
||||
if (!filter)
|
||||
return nodes;
|
||||
|
||||
var mapped = nodes.map(function(node) {
|
||||
return [[node[0]], node[1]];
|
||||
});
|
||||
|
||||
return build_longest_common_substring(mapped, filter);
|
||||
}, //}}}
|
||||
|
||||
javascript: function(str)
|
||||
{
|
||||
g_substrings = [];
|
||||
var matches = str.match(/^(.*?)(\s*\.\s*)?(\w*)$/);
|
||||
var object = "window";
|
||||
var filter = matches[3] || "";
|
||||
var start = matches[1].length-1;
|
||||
if (matches[2])
|
||||
{
|
||||
var brackets = 0, parentheses = 0;
|
||||
outer:
|
||||
for (; start >= 0; start--)
|
||||
{
|
||||
switch (matches[1][start])
|
||||
{
|
||||
case ";":
|
||||
case "{":
|
||||
break outer;
|
||||
|
||||
case "]":
|
||||
brackets--;
|
||||
break;
|
||||
case "[":
|
||||
brackets++;
|
||||
break;
|
||||
case ")":
|
||||
parentheses--;
|
||||
break;
|
||||
case "(":
|
||||
parentheses++;
|
||||
break;
|
||||
}
|
||||
if (brackets > 0 || parentheses > 0)
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
object = matches[1].substr(start+1) || "window";
|
||||
|
||||
var completions = [];
|
||||
try
|
||||
{
|
||||
completions = eval(
|
||||
"var comp = [];" +
|
||||
"var type = '';" +
|
||||
"var value = '';" +
|
||||
"var obj = eval(" + object + ");" +
|
||||
"for (var i in obj) {" +
|
||||
" try { type = typeof(obj[i]); } catch (e) { type = 'unknown type'; };" +
|
||||
" if (type == 'number' || type == 'string' || type == 'boolean') {" +
|
||||
" value = obj[i];" +
|
||||
" comp.push([[i], type + ': ' + value]); }" +
|
||||
// The problem with that is that you complete vimperator.
|
||||
// but can't press <Tab> to complete sub items
|
||||
// so it's better to complete vimperator and the user can do
|
||||
// .<tab> to get the sub items
|
||||
//" else if (type == 'function') {" +
|
||||
//" comp.push([[i+'('], type]); }" +
|
||||
//" else if (type == 'object') {" +
|
||||
//" comp.push([[i+'.'], type]); }" +
|
||||
" else {" +
|
||||
" comp.push([[i], type]); }" +
|
||||
"} comp;");
|
||||
} catch (e) { completions = []; };
|
||||
|
||||
return build_longest_starting_substring(completions, filter);
|
||||
},
|
||||
|
||||
exTabCompletion: function(str) //{{{
|
||||
{
|
||||
var [count, cmd, special, args] = vimperator.commands.parseCommand(str);
|
||||
var completions = [];
|
||||
var start = 0;
|
||||
|
||||
// if there is no space between the command name and the cursor
|
||||
// then get completions of the command name
|
||||
var matches = str.match(/^(:*\d*)\w*$/);
|
||||
if (matches)
|
||||
{
|
||||
completions = this.get_command_completions(cmd);
|
||||
start = matches[1].length;
|
||||
}
|
||||
else // dynamically get completions as specified with the command's completer function
|
||||
{
|
||||
var command = vimperator.commands.get(cmd);
|
||||
if (command && command.completer)
|
||||
{
|
||||
matches = str.match(/^:*\d*\w+\s+/);
|
||||
start = matches ? matches[0].length : 0;
|
||||
|
||||
// TODO: maybe we should move these checks to the complete functions
|
||||
if (command.hasName("open") || command.hasName("tabopen") || command.hasName("winopen"))
|
||||
{
|
||||
var skip = args.match(/^(.*,\s+)(.*)/); // start after the last ", "
|
||||
if (skip)
|
||||
{
|
||||
start += skip[1].length;
|
||||
args = skip[2];
|
||||
}
|
||||
}
|
||||
else if (command.hasName("echo") || command.hasName("echoerr") || command.hasName("javascript"))
|
||||
{
|
||||
var skip = args.match(/^(.*?)(\w*)$/); // start at beginning of the last word
|
||||
if (skip)
|
||||
start += skip[1].length;
|
||||
}
|
||||
|
||||
completions = command.completer.call(this, args);
|
||||
}
|
||||
}
|
||||
return [start, completions];
|
||||
} //}}}
|
||||
|
||||
}
|
||||
})(); // }}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
306
content/editor.js
Normal file
306
content/editor.js
Normal file
@@ -0,0 +1,306 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
*
|
||||
* Mozilla Public License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License
|
||||
* Version 1.1 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License
|
||||
* at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS"
|
||||
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
// command names taken from:
|
||||
// http://developer.mozilla.org/en/docs/Editor_Embedding_Guide
|
||||
|
||||
function Editor() //{{{
|
||||
{
|
||||
// store our last search with f,F,t or T
|
||||
var last_findChar = null;
|
||||
var last_findChar_func = null;
|
||||
|
||||
function editor()
|
||||
{
|
||||
return window.document.commandDispatcher.focusedElement;
|
||||
}
|
||||
|
||||
function getController()
|
||||
{
|
||||
var ed = editor();
|
||||
if (!ed || !ed.controllers)
|
||||
return null;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
this.col = function()
|
||||
{
|
||||
var col = 1;
|
||||
var text = editor().value;
|
||||
for (var i = 0; i < editor().selectionStart; i++)
|
||||
{
|
||||
col++;
|
||||
if (text[i] == "\n")
|
||||
col = 1;
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
controller.doCommand(cmd);
|
||||
did_command = true;
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
if (!did_command)
|
||||
vimperator.beep();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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!
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
} //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
911
content/events.js
Normal file
911
content/events.js
Normal file
@@ -0,0 +1,911 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
function Events() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// this handler is for middle click only in the content
|
||||
//window.addEventListener("mousedown", onVimperatorKeypress, true);
|
||||
//content.mPanelContainer.addEventListener("mousedown", onVimperatorKeypress, true);
|
||||
//document.getElementById("content").onclick = function(event) { alert("foo"); };
|
||||
|
||||
// any tab related events
|
||||
var tabcontainer = getBrowser().tabContainer;
|
||||
tabcontainer.addEventListener("TabMove", function(event) {
|
||||
vimperator.statusline.updateTabCount()
|
||||
vimperator.buffer.updateBufferList();
|
||||
}, false);
|
||||
tabcontainer.addEventListener("TabOpen", function(event) {
|
||||
vimperator.statusline.updateTabCount();
|
||||
vimperator.buffer.updateBufferList();
|
||||
}, false);
|
||||
tabcontainer.addEventListener("TabClose", function(event) {
|
||||
vimperator.statusline.updateTabCount()
|
||||
vimperator.buffer.updateBufferList();
|
||||
}, false);
|
||||
tabcontainer.addEventListener("TabSelect", function(event) {
|
||||
vimperator.statusline.updateTabCount();
|
||||
vimperator.buffer.updateBufferList();
|
||||
vimperator.tabs.updateSelectionHistory();
|
||||
setTimeout(vimperator.focusContent, 10); // just make sure, that no widget has focus
|
||||
}, false);
|
||||
|
||||
// this adds an event which is is called on each page load, even if the
|
||||
// page is loaded in a background tab
|
||||
getBrowser().addEventListener("load", onPageLoad, true);
|
||||
|
||||
// called when the active document is scrolled
|
||||
getBrowser().addEventListener("scroll", function (event)
|
||||
{
|
||||
vimperator.statusline.updateBufferPosition();
|
||||
vimperator.modes.show();
|
||||
}, null);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// track if a popup is open or the menubar is active
|
||||
var active_menubar = false;
|
||||
function enterPopupMode(event)
|
||||
{
|
||||
if (event.originalTarget.localName == "tooltip" || event.originalTarget.id == "vimperator-visualbell")
|
||||
return;
|
||||
|
||||
vimperator.modes.add(vimperator.modes.MENU);
|
||||
}
|
||||
function exitPopupMode()
|
||||
{
|
||||
// gContextMenu is set to NULL by firefox, when a context menu is closed
|
||||
if (!gContextMenu && !active_menubar)
|
||||
vimperator.modes.remove(vimperator.modes.MENU);
|
||||
}
|
||||
function enterMenuMode()
|
||||
{
|
||||
active_menubar = true;
|
||||
vimperator.modes.add(vimperator.modes.MENU);
|
||||
}
|
||||
function exitMenuMode()
|
||||
{
|
||||
active_menubar = false;
|
||||
vimperator.modes.remove(vimperator.modes.MENU);
|
||||
}
|
||||
window.addEventListener("popupshown", enterPopupMode, true);
|
||||
window.addEventListener("popuphidden", exitPopupMode, true);
|
||||
window.addEventListener("DOMMenuBarActive", enterMenuMode, true);
|
||||
window.addEventListener("DOMMenuBarInactive", exitMenuMode, true);
|
||||
|
||||
// window.document.addEventListener("DOMTitleChanged", function(event)
|
||||
// {
|
||||
// vimperator.log("titlechanged");
|
||||
// }, null);
|
||||
|
||||
// NOTE: the order of ["Esc", "Escape"] or ["Escape", "Esc"]
|
||||
// matters, so use that string as the first item, that you
|
||||
// want to refer to within Vimperator's source code for
|
||||
// comparisons like if (key == "Esc") { ... }
|
||||
var keyTable = [
|
||||
[ KeyEvent.DOM_VK_ESCAPE, ["Esc", "Escape"] ],
|
||||
[ KeyEvent.DOM_VK_LEFT_SHIFT, ["<"] ],
|
||||
[ KeyEvent.DOM_VK_RIGHT_SHIFT, [">"] ],
|
||||
[ KeyEvent.DOM_VK_RETURN, ["Return", "CR", "Enter"] ],
|
||||
[ KeyEvent.DOM_VK_TAB, ["Tab"] ],
|
||||
[ KeyEvent.DOM_VK_DELETE, ["Del"] ],
|
||||
[ KeyEvent.DOM_VK_BACK_SPACE, ["BS"] ],
|
||||
[ KeyEvent.DOM_VK_HOME, ["Home"] ],
|
||||
[ KeyEvent.DOM_VK_INSERT, ["Insert", "Ins"] ],
|
||||
[ KeyEvent.DOM_VK_END, ["End"] ],
|
||||
[ KeyEvent.DOM_VK_LEFT, ["Left"] ],
|
||||
[ KeyEvent.DOM_VK_RIGHT, ["Right"] ],
|
||||
[ KeyEvent.DOM_VK_UP, ["Up"] ],
|
||||
[ KeyEvent.DOM_VK_DOWN, ["Down"] ],
|
||||
[ KeyEvent.DOM_VK_PAGE_UP, ["PageUp"] ],
|
||||
[ KeyEvent.DOM_VK_PAGE_DOWN, ["PageDown"] ],
|
||||
[ KeyEvent.DOM_VK_F1, ["F1"] ],
|
||||
[ KeyEvent.DOM_VK_F2, ["F2"] ],
|
||||
[ KeyEvent.DOM_VK_F3, ["F3"] ],
|
||||
[ KeyEvent.DOM_VK_F4, ["F4"] ],
|
||||
[ KeyEvent.DOM_VK_F5, ["F5"] ],
|
||||
[ KeyEvent.DOM_VK_F6, ["F6"] ],
|
||||
[ KeyEvent.DOM_VK_F7, ["F7"] ],
|
||||
[ KeyEvent.DOM_VK_F8, ["F8"] ],
|
||||
[ KeyEvent.DOM_VK_F9, ["F9"] ],
|
||||
[ KeyEvent.DOM_VK_F10, ["F10"] ],
|
||||
[ KeyEvent.DOM_VK_F11, ["F11"] ],
|
||||
[ KeyEvent.DOM_VK_F12, ["F12"] ],
|
||||
[ KeyEvent.DOM_VK_F13, ["F13"] ],
|
||||
[ KeyEvent.DOM_VK_F14, ["F14"] ],
|
||||
[ KeyEvent.DOM_VK_F15, ["F15"] ],
|
||||
[ KeyEvent.DOM_VK_F16, ["F16"] ],
|
||||
[ KeyEvent.DOM_VK_F17, ["F17"] ],
|
||||
[ KeyEvent.DOM_VK_F18, ["F18"] ],
|
||||
[ KeyEvent.DOM_VK_F19, ["F19"] ],
|
||||
[ KeyEvent.DOM_VK_F20, ["F20"] ],
|
||||
[ KeyEvent.DOM_VK_F21, ["F21"] ],
|
||||
[ KeyEvent.DOM_VK_F22, ["F22"] ],
|
||||
[ KeyEvent.DOM_VK_F23, ["F23"] ],
|
||||
[ KeyEvent.DOM_VK_F24, ["F24"] ],
|
||||
];
|
||||
|
||||
function getKeyCode(str)
|
||||
{
|
||||
str = str.toLowerCase();
|
||||
for (var i in keyTable)
|
||||
{
|
||||
for (var k in keyTable[i][1])
|
||||
{
|
||||
// we don't store lowercase keys in the keyTable, because we
|
||||
// also need to get good looking strings for the reverse action
|
||||
if (keyTable[i][1][k].toLowerCase() == str)
|
||||
return keyTable[i][0];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isFormElemFocused()
|
||||
{
|
||||
var elt = window.document.commandDispatcher.focusedElement;
|
||||
if (elt == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{ // sometimes the elt doesn't have .localName
|
||||
var tagname = elt.localName.toLowerCase();
|
||||
var type = elt.type.toLowerCase();
|
||||
|
||||
if ( (tagname == "input" && (type != "image")) ||
|
||||
tagname == "textarea" ||
|
||||
// tagName == "SELECT" ||
|
||||
// tagName == "BUTTON" ||
|
||||
tagname == "isindex") // isindex is a deprecated one-line input box
|
||||
return true;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// FIXME: do nothing?
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function onPageLoad(event)
|
||||
{
|
||||
if (event.originalTarget instanceof HTMLDocument)
|
||||
{
|
||||
var doc = event.originalTarget;
|
||||
// document is part of a frameset
|
||||
if (doc.defaultView.frameElement)
|
||||
{
|
||||
// hacky way to get rid of "Transfering data from ..." on sites with frames
|
||||
// when you click on a link inside a frameset, because asyncUpdateUI
|
||||
// is not triggered there (firefox bug?)
|
||||
setTimeout(vimperator.statusline.updateUrl, 10);
|
||||
return;
|
||||
}
|
||||
|
||||
// code which should happen for all (also background) newly loaded tabs goes here:
|
||||
vimperator.buffer.updateBufferList();
|
||||
|
||||
//update history
|
||||
var url = vimperator.buffer.URL;
|
||||
var title = vimperator.buffer.title;
|
||||
vimperator.history.add(url, title);
|
||||
|
||||
// code which is only relevant if the page load is the current tab goes here:
|
||||
if (doc == getBrowser().selectedBrowser.contentDocument)
|
||||
{
|
||||
// we want to stay in command mode after a page has loaded
|
||||
// TODO: remember the last focused input widget, so we can go there with 'gi'
|
||||
setTimeout(vimperator.focusContent, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
this.wantsModeReset = true; // used in onFocusChange since Firefox is so buggy here
|
||||
|
||||
this.destroy = function()
|
||||
{
|
||||
// removeEventListeners() to avoid mem leaks
|
||||
window.dump("TODO: remove all eventlisteners\n");
|
||||
|
||||
getBrowser().removeProgressListener(this.progressListener);
|
||||
|
||||
window.removeEventListener("popupshown", enterPopupMode, true);
|
||||
window.removeEventListener("popuphidden", exitPopupMode, true);
|
||||
window.removeEventListener("DOMMenuBarActive", enterMenuMode, true);
|
||||
window.removeEventListener("DOMMenuBarInactive", exitMenuMode, true);
|
||||
|
||||
window.removeEventListener("keypress", this.onKeyPress, true);
|
||||
window.removeEventListener("keydown", this.onKeyDown, true);
|
||||
}
|
||||
|
||||
// This method pushes keys into the event queue from vimperator
|
||||
// it is similar to vim's feedkeys() method, but cannot cope with
|
||||
// 2 partially feeded strings, you have to feed one parsable string
|
||||
//
|
||||
// @param keys: a string like "2<C-f>" to pass
|
||||
// if you want < to be taken literally, prepend it with a \\
|
||||
this.feedkeys = function(keys)
|
||||
{
|
||||
var doc = window.document;
|
||||
var view = window.document.defaultView;
|
||||
var escapeKey = false; // \ to escape some special keys
|
||||
|
||||
for (var i = 0; i < keys.length; i++)
|
||||
{
|
||||
var charCode = keys.charCodeAt(i);
|
||||
var keyCode = 0;
|
||||
var shift = false, ctrl = false, alt = false, meta = false;
|
||||
//if (charCode == 92) // the '\' key FIXME: support the escape key
|
||||
if (charCode == 60 && !escapeKey) // the '<' key starts a complex key
|
||||
{
|
||||
var matches = keys.substr(i+1).match(/([CSMAcsma]-)*([^>]+)/);
|
||||
if (matches && matches[2])
|
||||
{
|
||||
if (matches[1]) // check for modifiers
|
||||
{
|
||||
ctrl = /[cC]-/.test(matches[1]);
|
||||
alt = /[aA]-/.test(matches[1]);
|
||||
shift = /[sS]-/.test(matches[1]);
|
||||
meta = /[mM]-/.test(matches[1]);
|
||||
}
|
||||
if (matches[2].length == 1)
|
||||
{
|
||||
if (!ctrl && !alt && !shift && !meta)
|
||||
return; // an invalid key like <a>
|
||||
charCode = matches[2].charCodeAt(0);
|
||||
}
|
||||
else if (matches[2].toLowerCase() == "space")
|
||||
{
|
||||
charCode = 32;
|
||||
}
|
||||
else if (keyCode = getKeyCode(matches[2]))
|
||||
{
|
||||
charCode = 0;
|
||||
}
|
||||
else //an invalid key like <A-xxx> was found, stop propagation here (like vim)
|
||||
return;
|
||||
|
||||
i += matches[0].length + 1;
|
||||
}
|
||||
}
|
||||
|
||||
var elem = window.document.commandDispatcher.focusedElement;
|
||||
if (!elem)
|
||||
elem = window.content;
|
||||
|
||||
var evt = doc.createEvent("KeyEvents");
|
||||
evt.initKeyEvent("keypress", true, true, view, ctrl, alt, shift, meta, keyCode, charCode );
|
||||
elem.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
// this function converts the given event to
|
||||
// a keycode which can be used in mappings
|
||||
// e.g. pressing ctrl+n would result in the string "<C-n>"
|
||||
// null if unknown key
|
||||
this.toString = function(event) //{{{
|
||||
{
|
||||
if (!event)
|
||||
return;
|
||||
|
||||
var key = null;
|
||||
var modifier = "";
|
||||
|
||||
if (event.ctrlKey)
|
||||
modifier += "C-";
|
||||
if (event.altKey)
|
||||
modifier += "A-";
|
||||
if (event.metaKey)
|
||||
modifier += "M-";
|
||||
|
||||
if (event.type == "keypress")
|
||||
{
|
||||
if (event.charCode == 0)
|
||||
{
|
||||
if (event.shiftKey)
|
||||
modifier += "S-";
|
||||
|
||||
for (var i in keyTable)
|
||||
{
|
||||
if (keyTable[i][0] == event.keyCode)
|
||||
{
|
||||
key = keyTable[i][1][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// special handling of the Space key
|
||||
else if (event.charCode == 32)
|
||||
{
|
||||
if (event.shiftKey)
|
||||
modifier += "S-";
|
||||
key = "Space";
|
||||
}
|
||||
// a normal key like a, b, c, 0, etc.
|
||||
else if (event.charCode > 0)
|
||||
{
|
||||
key = String.fromCharCode(event.charCode);
|
||||
if (modifier.length == 0)
|
||||
return key;
|
||||
}
|
||||
|
||||
if (key == null)
|
||||
return null;
|
||||
|
||||
}
|
||||
else if (event.type == "click" || event.type == "dblclick")
|
||||
{
|
||||
if (event.shiftKey)
|
||||
modifier += "S-";
|
||||
if (event.type == "dblclick")
|
||||
modifier += "2-";
|
||||
// TODO: triple and quadruple click
|
||||
|
||||
switch (event.button)
|
||||
{
|
||||
case 0:
|
||||
key = "LeftMouse"
|
||||
break;
|
||||
case 1:
|
||||
key = "MiddleMouse"
|
||||
break;
|
||||
case 2:
|
||||
key = "RightMouse"
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == null)
|
||||
return null;
|
||||
|
||||
// a key like F1 is always enclosed in < and >
|
||||
return "<" + modifier + key + ">";
|
||||
|
||||
} //}}}
|
||||
|
||||
this.isAcceptKey = function(key)
|
||||
{
|
||||
return (key == "<Return>" || key == "<C-j>" || key == "<C-m>");
|
||||
}
|
||||
this.isCancelKey = function(key)
|
||||
{
|
||||
return (key == "<Esc>" || key == "<C-[>" || key == "<C-c>");
|
||||
}
|
||||
|
||||
// argument "event" is delibarately not used, as i don't seem to have
|
||||
// access to the real focus target
|
||||
//
|
||||
// the ugly wantsModeReset is needed, because firefox generates a massive
|
||||
// amount of focus changes for things like <C-v><C-k> (focusing the search field)
|
||||
this.onFocusChange = function(event)
|
||||
{
|
||||
// command line has it's own focus change handler
|
||||
if (vimperator.mode == vimperator.modes.COMMAND_LINE)
|
||||
return;
|
||||
|
||||
var elem = window.document.commandDispatcher.focusedElement;
|
||||
if (elem && elem.readOnly)
|
||||
return;
|
||||
|
||||
if (elem && elem instanceof HTMLInputElement &&
|
||||
(elem.type.toLowerCase() == "text" || elem.type.toLowerCase() == "password"))
|
||||
{
|
||||
this.wantsModeReset = false;
|
||||
vimperator.mode = vimperator.modes.INSERT;
|
||||
vimperator.buffer.lastInputField = elem;
|
||||
}
|
||||
else if (elem && elem instanceof HTMLTextAreaElement)
|
||||
{
|
||||
this.wantsModeReset = false;
|
||||
if (vimperator.options["insertmode"])
|
||||
vimperator.modes.set(vimperator.modes.INSERT, vimperator.modes.TEXTAREA);
|
||||
else if (elem.selectionEnd - elem.selectionStart > 0)
|
||||
vimperator.modes.set(vimperator.modes.VISUAL, vimperator.modes.TEXTAREA);
|
||||
else
|
||||
vimperator.modes.main = vimperator.modes.TEXTAREA;
|
||||
vimperator.buffer.lastInputField = elem;
|
||||
}
|
||||
else if (vimperator.mode == vimperator.modes.INSERT ||
|
||||
vimperator.mode == vimperator.modes.TEXTAREA ||
|
||||
vimperator.mode == vimperator.modes.VISUAL)
|
||||
{
|
||||
this.wantsModeReset = true;
|
||||
setTimeout(function() {
|
||||
if (vimperator.events.wantsModeReset)
|
||||
vimperator.modes.reset();
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
this.onSelectionChange = function(event)
|
||||
{
|
||||
var could_copy = false;
|
||||
var controller = document.commandDispatcher.getControllerForCommand("cmd_copy");
|
||||
if (controller && controller.isCommandEnabled("cmd_copy"))
|
||||
could_copy = true;
|
||||
|
||||
if (vimperator.mode != vimperator.modes.VISUAL)
|
||||
{
|
||||
if (could_copy)
|
||||
{
|
||||
if ((vimperator.mode == vimperator.modes.TEXTAREA || (vimperator.modes.extended & vimperator.modes.TEXTAREA))
|
||||
&& !vimperator.options["insertmode"])
|
||||
vimperator.modes.set(vimperator.modes.VISUAL, vimperator.modes.TEXTAREA);
|
||||
else if (vimperator.mode == vimperator.modes.CARET)
|
||||
vimperator.modes.set(vimperator.modes.VISUAL, vimperator.modes.CARET);
|
||||
}
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// if (!could_copy && vimperator.modes.extended & vimperator.modes.CARET)
|
||||
// vimperator.mode = vimperator.modes.CARET;
|
||||
//}
|
||||
}
|
||||
|
||||
// global escape handler, is called in ALL modes
|
||||
this.onEscape = function()
|
||||
{
|
||||
if (!vimperator.modes.passNextKey)
|
||||
{
|
||||
if (vimperator.modes.passAllKeys)
|
||||
{
|
||||
vimperator.modes.passAllKeys = false;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (vimperator.mode)
|
||||
{
|
||||
case vimperator.modes.VISUAL:
|
||||
if (vimperator.modes.extended & vimperator.modes.TEXTAREA)
|
||||
vimperator.mode = vimperator.modes.TEXTAREA;
|
||||
else if (vimperator.modes.extended & vimperator.modes.CARET)
|
||||
vimperator.mode = vimperator.modes.CARET;
|
||||
break;
|
||||
|
||||
case vimperator.modes.CARET:
|
||||
// setting this option will trigger an observer which will care about all other details
|
||||
// like setting the NORMAL mode
|
||||
Options.setFirefoxPref("accessibility.browsewithcaret", false);
|
||||
break;
|
||||
|
||||
case vimperator.modes.INSERT:
|
||||
if ((vimperator.modes.extended & vimperator.modes.TEXTAREA) && !vimperator.options["insertmode"])
|
||||
vimperator.mode = vimperator.modes.TEXTAREA;
|
||||
else
|
||||
vimperator.modes.reset();
|
||||
break;
|
||||
|
||||
case vimperator.modes.COMMAND_LINE:
|
||||
vimperator.commandline.close();
|
||||
vimperator.modes.reset();
|
||||
break;
|
||||
|
||||
default:
|
||||
// clear any selection made
|
||||
var selection = window.content.getSelection();
|
||||
try { // a simple if (selection) does not work
|
||||
selection.collapseToStart();
|
||||
} catch (e) { }
|
||||
vimperator.commandline.clear();
|
||||
|
||||
vimperator.modes.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this keypress handler gets always called first, even if e.g.
|
||||
// the commandline has focus
|
||||
this.onKeyPress = function(event)
|
||||
{
|
||||
var key = vimperator.events.toString(event);
|
||||
if (!key)
|
||||
return true;
|
||||
|
||||
var stop = true; // set to false if we should NOT consume this event but let also firefox handle it
|
||||
|
||||
// menus have their own command handlers
|
||||
if (vimperator.modes.extended & vimperator.modes.MENU)
|
||||
return true;
|
||||
|
||||
// handle Escape-one-key mode (Ctrl-v)
|
||||
if (vimperator.modes.passNextKey && !vimperator.modes.passAllKeys)
|
||||
{
|
||||
vimperator.modes.passNextKey = false;
|
||||
return true;
|
||||
}
|
||||
// handle Escape-all-keys mode (Ctrl-q)
|
||||
if (vimperator.modes.passAllKeys)
|
||||
{
|
||||
if (vimperator.modes.passNextKey)
|
||||
vimperator.modes.passNextKey = false; // and then let flow continue
|
||||
else if (key == "<Esc>" || key == "<C-[>" || key == "<C-v>")
|
||||
; // let flow continue to handle these keys to cancel escape-all-keys mode
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME: proper way is to have a better onFocus handler which also handles events for the XUL
|
||||
if (!vimperator.mode == vimperator.modes.TEXTAREA &&
|
||||
!vimperator.mode == vimperator.modes.INSERT &&
|
||||
!vimperator.mode == vimperator.modes.COMMAND_LINE &&
|
||||
isFormElemFocused()) // non insert mode, but e.g. the location bar has focus
|
||||
return true;
|
||||
|
||||
if (vimperator.mode == vimperator.modes.COMMAND_LINE &&
|
||||
(vimperator.modes.extended & vimperator.modes.OUTPUT_MULTILINE))
|
||||
{
|
||||
vimperator.commandline.onMultilineOutputEvent(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
// XXX: ugly hack for now pass certain keys to firefox as they are without beeping
|
||||
// also fixes key navigation in combo boxes, etc.
|
||||
if (vimperator.mode == vimperator.modes.NORMAL)
|
||||
{
|
||||
if (key == "<Tab>" || key == "<S-Tab>" || key == "<Return>" || key == "<Space>" || key == "<Up>" || key == "<Down>")
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// // FIXME: handle middle click in content area {{{
|
||||
// // alert(event.target.id);
|
||||
// if (/*event.type == 'mousedown' && */event.button == 1 && event.target.id == 'content')
|
||||
// {
|
||||
// //echo("foo " + event.target.id);
|
||||
// //if (document.commandDispatcher.focusedElement == command_line.inputField)
|
||||
// {
|
||||
// //alert(command_line.value.substring(0, command_line.selectionStart));
|
||||
// command_line.value = command_line.value.substring(0, command_line.selectionStart) +
|
||||
// readFromClipboard() +
|
||||
// command_line.value.substring(command_line.selectionEnd, command_line.value.length);
|
||||
// alert(command_line.value);
|
||||
// }
|
||||
// //else
|
||||
// // {
|
||||
// // openURLs(readFromClipboard());
|
||||
// // }
|
||||
// return true;
|
||||
// } }}}
|
||||
|
||||
|
||||
// if Hit-a-hint mode is on, special handling of keys is required
|
||||
// FIXME: total mess
|
||||
if (vimperator.mode == vimperator.modes.HINTS)
|
||||
{
|
||||
// never propagate this key to firefox, when hints are visible
|
||||
//event.preventDefault();
|
||||
//event.stopPropagation();
|
||||
|
||||
var map = vimperator.mappings.get(vimperator.modes.HINTS, key);
|
||||
if (map)
|
||||
{
|
||||
if (map.always_active || vimperator.hints.currentState() == 1)
|
||||
{
|
||||
map.execute(null, vimperator.input.count);
|
||||
if (map.cancel_mode) // stop processing this event
|
||||
{
|
||||
vimperator.hints.disableHahMode();
|
||||
vimperator.input.buffer = "";
|
||||
vimperator.statusline.updateInputBuffer("");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: make sure that YOU update the statusbar message yourself
|
||||
// first in g_hint_mappings when in this mode!
|
||||
vimperator.statusline.updateInputBuffer(vimperator.input.buffer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no mapping found, beep()
|
||||
if (vimperator.hints.currentState() == 1)
|
||||
{
|
||||
vimperator.beep();
|
||||
vimperator.hints.disableHahMode();
|
||||
vimperator.input.buffer = "";
|
||||
vimperator.statusline.updateInputBuffer(vimperator.input.buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we came here, let hit-a-hint process the key as it is part
|
||||
// of a partial link
|
||||
var res = vimperator.hints.processEvent(event);
|
||||
if (res < 0) // error occured processing this key
|
||||
{
|
||||
vimperator.beep();
|
||||
if (vimperator.modes.extended & vimperator.modes.QUICK_HINT)
|
||||
vimperator.hints.disableHahMode();
|
||||
else // ALWAYS mode
|
||||
vimperator.hints.resetHintedElements();
|
||||
vimperator.input.buffer = "";
|
||||
}
|
||||
else if (res == 0 || vimperator.modes.extended & vimperator.modes.EXTENDED_HINT) // key processed, part of a larger hint
|
||||
vimperator.input.buffer += key;
|
||||
else // this key completed a quick hint
|
||||
{
|
||||
// if the hint is all in UPPERCASE, open it in new tab
|
||||
vimperator.input.buffer += key;
|
||||
if (/[A-Za-z]/.test(vimperator.input.buffer) && vimperator.input.buffer.toUpperCase() == vimperator.input.buffer)
|
||||
vimperator.hints.openHints(true, false);
|
||||
else // open in current window
|
||||
vimperator.hints.openHints(false, false);
|
||||
|
||||
if (vimperator.modes.extended & vimperator.modes.QUICK_HINT)
|
||||
vimperator.hints.disableHahMode();
|
||||
else // ALWAYS mode
|
||||
vimperator.hints.resetHintedElements();
|
||||
|
||||
vimperator.input.buffer = "";
|
||||
}
|
||||
|
||||
vimperator.statusline.updateInputBuffer(vimperator.input.buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
var count_str = vimperator.input.buffer.match(/^[0-9]*/)[0];
|
||||
var candidate_command = (vimperator.input.buffer + key).replace(count_str, '');
|
||||
var map;
|
||||
|
||||
// counts must be at the start of a complete mapping (10j -> go 10 lines down)
|
||||
if ((vimperator.input.buffer + key).match(/^[1-9][0-9]*$/))
|
||||
{
|
||||
// no count for insert mode mappings
|
||||
if (vimperator.mode == vimperator.modes.INSERT || vimperator.mode == vimperator.modes.COMMAND_LINE)
|
||||
stop = false;
|
||||
else
|
||||
vimperator.input.buffer += key;
|
||||
}
|
||||
else if (vimperator.input.pendingArgMap)
|
||||
{
|
||||
vimperator.input.buffer = "";
|
||||
|
||||
if (key != "<Esc>" && key != "<C-[>")
|
||||
vimperator.input.pendingArgMap.execute(null, vimperator.input.count, key);
|
||||
|
||||
vimperator.input.pendingArgMap = null;
|
||||
}
|
||||
else if (map = vimperator.mappings.get(vimperator.mode, candidate_command))
|
||||
{
|
||||
vimperator.input.count = parseInt(count_str, 10);
|
||||
if (isNaN(vimperator.input.count))
|
||||
vimperator.input.count = -1;
|
||||
if (map.flags & Mappings.flags.ARGUMENT)
|
||||
{
|
||||
vimperator.input.pendingArgMap = map;
|
||||
vimperator.input.buffer += key;
|
||||
}
|
||||
else if (vimperator.input.pendingMotionMap)
|
||||
{
|
||||
if (key != "<Esc>" && key != "<C-[>")
|
||||
{
|
||||
vimperator.input.pendingMotionMap.execute(candidate_command, vimperator.input.count, null);
|
||||
}
|
||||
vimperator.input.pendingMotionMap = null;
|
||||
vimperator.input.buffer = "";
|
||||
}
|
||||
// no count support for these commands yet
|
||||
else if (map.flags & Mappings.flags.MOTION)
|
||||
{
|
||||
vimperator.input.pendingMotionMap = map;
|
||||
vimperator.input.buffer = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
vimperator.input.buffer = "";
|
||||
// vimperator.log("executed: " + candidate_command + " in mode: " + mode, 8);
|
||||
map.execute(null, vimperator.input.count);
|
||||
}
|
||||
}
|
||||
else if (vimperator.mappings.getCandidates(vimperator.mode, candidate_command).length > 0)
|
||||
{
|
||||
vimperator.input.buffer += key;
|
||||
}
|
||||
else
|
||||
{
|
||||
vimperator.input.buffer = "";
|
||||
vimperator.input.pendingArgMap = null;
|
||||
vimperator.input.pendingMotionMap = null;
|
||||
|
||||
if (key != "<Esc>" && key != "<C-[>")
|
||||
{
|
||||
stop = false; // command was not a vimperator command, maybe it is a firefox command
|
||||
|
||||
// TODO: see if this check is needed or are all motion commands already mapped in these modes?
|
||||
if (vimperator.mode != vimperator.modes.INSERT && vimperator.mode != vimperator.modes.COMMAND_LINE)
|
||||
vimperator.beep();
|
||||
}
|
||||
}
|
||||
|
||||
if (stop)
|
||||
{
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
var motion_map = (vimperator.input.pendingMotionMap && vimperator.input.pendingMotionMap.names[0]) || "";
|
||||
vimperator.statusline.updateInputBuffer(motion_map + vimperator.input.buffer);
|
||||
return false;
|
||||
}
|
||||
window.addEventListener("keypress", this.onKeyPress, true);
|
||||
|
||||
// this is need for sites like msn.com which focus the input field on keydown
|
||||
this.onKeyDown = function(event)
|
||||
{
|
||||
if (vimperator.modes.passNextKey ^ vimperator.modes.passAllKeys || isFormElemFocused())
|
||||
return true;
|
||||
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
window.addEventListener("keydown", this.onKeyDown, true);
|
||||
|
||||
this.progressListener =
|
||||
{
|
||||
QueryInterface: function(aIID)
|
||||
{
|
||||
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
|
||||
aIID.equals(Components.interfaces.nsIXULBrowserWindow) || // for setOverLink();
|
||||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
|
||||
aIID.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
throw Components.results.NS_NOINTERFACE;
|
||||
},
|
||||
|
||||
// XXX: function may later be needed to detect a canceled synchronous openURL()
|
||||
onStateChange: function(webProgress, aRequest, flags, aStatus)
|
||||
{
|
||||
// STATE_IS_DOCUMENT | STATE_IS_WINDOW is important, because we also
|
||||
// receive statechange events for loading images and other parts of the web page
|
||||
if (flags & (Components.interfaces.nsIWebProgressListener.STATE_IS_DOCUMENT |
|
||||
Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW))
|
||||
{
|
||||
// This fires when the load event is initiated
|
||||
if (flags & Components.interfaces.nsIWebProgressListener.STATE_START)
|
||||
{
|
||||
vimperator.statusline.updateProgress(0);
|
||||
}
|
||||
else if (flags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
|
||||
;// vimperator.statusline.updateUrl();
|
||||
}
|
||||
},
|
||||
// for notifying the user about secure web pages
|
||||
onSecurityChange: function (webProgress, aRequest, aState)
|
||||
{
|
||||
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
|
||||
if (aState & nsIWebProgressListener.STATE_IS_INSECURE)
|
||||
vimperator.statusline.setClass("insecure");
|
||||
else if (aState & nsIWebProgressListener.STATE_IS_BROKEN)
|
||||
vimperator.statusline.setClass("broken");
|
||||
else if (aState & nsIWebProgressListener.STATE_IS_SECURE)
|
||||
vimperator.statusline.setClass("secure");
|
||||
},
|
||||
onStatusChange: function(webProgress, request, status, message)
|
||||
{
|
||||
vimperator.statusline.updateUrl(message);
|
||||
},
|
||||
onProgressChange: function(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress)
|
||||
{
|
||||
vimperator.statusline.updateProgress(curTotalProgress/maxTotalProgress);
|
||||
},
|
||||
// happens when the users switches tabs
|
||||
onLocationChange: function()
|
||||
{
|
||||
vimperator.statusline.updateUrl();
|
||||
vimperator.statusline.updateProgress();
|
||||
|
||||
// if this is not delayed we get the position of the old buffer
|
||||
setTimeout(function() { vimperator.statusline.updateBufferPosition(); }, 100);
|
||||
},
|
||||
// called at the very end of a page load
|
||||
asyncUpdateUI: function()
|
||||
{
|
||||
setTimeout(vimperator.statusline.updateUrl, 100);
|
||||
},
|
||||
setOverLink : function(link, b)
|
||||
{
|
||||
var ssli = vimperator.options["showstatuslinks"];
|
||||
if (link && ssli)
|
||||
{
|
||||
if (ssli == 1)
|
||||
vimperator.statusline.updateUrl("Link: " + link);
|
||||
else if (ssli == 2)
|
||||
vimperator.echo("Link: " + link);
|
||||
}
|
||||
|
||||
if (link == "")
|
||||
{
|
||||
if (ssli == 1)
|
||||
vimperator.statusline.updateUrl();
|
||||
else if (ssli == 2)
|
||||
vimperator.modes.show();
|
||||
}
|
||||
},
|
||||
|
||||
// stub functions for the interfaces
|
||||
setJSStatus : function(status) { ; },
|
||||
setJSDefaultStatus : function(status) { ; },
|
||||
setDefaultStatus : function(status) { ; },
|
||||
onLinkIconAvailable: function() { ; }
|
||||
};
|
||||
|
||||
window.XULBrowserWindow = this.progressListener;
|
||||
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIWebNavigation)
|
||||
.QueryInterface(Components.interfaces.nsIDocShellTreeItem).treeOwner
|
||||
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIXULWindow)
|
||||
.XULBrowserWindow = window.XULBrowserWindow;
|
||||
getBrowser().addProgressListener(this.progressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||||
|
||||
|
||||
this.prefObserver =
|
||||
{
|
||||
register: function()
|
||||
{
|
||||
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefService);
|
||||
this._branch = prefService.getBranch(""); // better way to monitor all changes?
|
||||
this._branch.QueryInterface(Components.interfaces.nsIPrefBranch2);
|
||||
this._branch.addObserver("", this, false);
|
||||
},
|
||||
|
||||
unregister: function()
|
||||
{
|
||||
if (!this._branch) return;
|
||||
this._branch.removeObserver("", this);
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData)
|
||||
{
|
||||
if (aTopic != "nsPref:changed") return;
|
||||
// aSubject is the nsIPrefBranch we're observing (after appropriate QI)
|
||||
// aData is the name of the pref that's been changed (relative to aSubject)
|
||||
switch (aData)
|
||||
{
|
||||
case "accessibility.browsewithcaret":
|
||||
var value = Options.getFirefoxPref("accessibility.browsewithcaret", false);
|
||||
vimperator.mode = value ? vimperator.modes.CARET : vimperator.modes.NORMAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.prefObserver.register();
|
||||
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
174
content/file.js
Normal file
174
content/file.js
Normal file
@@ -0,0 +1,174 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
Code based on venkman
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
|
||||
const PERM_IWOTH = 00002; /* write permission, others */
|
||||
const PERM_IWGRP = 00020; /* write permission, group */
|
||||
|
||||
const MODE_RDONLY = 0x01;
|
||||
const MODE_WRONLY = 0x02;
|
||||
const MODE_RDWR = 0x04;
|
||||
const MODE_CREATE = 0x08;
|
||||
const MODE_APPEND = 0x10;
|
||||
const MODE_TRUNCATE = 0x20;
|
||||
const MODE_SYNC = 0x40;
|
||||
const MODE_EXCL = 0x80;
|
||||
|
||||
|
||||
function LocalFile(file, mode, perms, tmp) // {{{
|
||||
{
|
||||
const classes = Components.classes;
|
||||
const interfaces = Components.interfaces;
|
||||
|
||||
const LOCALFILE_CTRID = "@mozilla.org/file/local;1";
|
||||
const FILEIN_CTRID = "@mozilla.org/network/file-input-stream;1";
|
||||
const FILEOUT_CTRID = "@mozilla.org/network/file-output-stream;1";
|
||||
const SCRIPTSTREAM_CTRID = "@mozilla.org/scriptableinputstream;1";
|
||||
|
||||
const nsIFile = interfaces.nsIFile;
|
||||
const nsILocalFile = interfaces.nsILocalFile;
|
||||
const nsIFileOutputStream = interfaces.nsIFileOutputStream;
|
||||
const nsIFileInputStream = interfaces.nsIFileInputStream;
|
||||
const nsIScriptableInputStream = interfaces.nsIScriptableInputStream;
|
||||
|
||||
if (typeof perms == "undefined")
|
||||
perms = 0666 & ~(PERM_IWOTH | PERM_IWGRP);
|
||||
|
||||
if (typeof mode == "string")
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case ">":
|
||||
mode = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
|
||||
break;
|
||||
case ">>":
|
||||
mode = MODE_WRONLY | MODE_CREATE | MODE_APPEND;
|
||||
break;
|
||||
case "<":
|
||||
mode = MODE_RDONLY;
|
||||
break;
|
||||
default:
|
||||
throw "Invalid mode ``" + mode + "''";
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof file == "string")
|
||||
{
|
||||
this.localFile = classes[LOCALFILE_CTRID].createInstance(nsILocalFile);
|
||||
this.localFile.initWithPath(file);
|
||||
if (!this.localFile.exists())
|
||||
throw "No such file or directory";
|
||||
}
|
||||
else if (file instanceof nsILocalFile)
|
||||
{
|
||||
this.localFile = file;
|
||||
if (!this.localFile.exists())
|
||||
throw "No such file or directory";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "bad type for argument |file|.";
|
||||
}
|
||||
|
||||
this.path = this.localFile.path;
|
||||
|
||||
if (mode & (MODE_WRONLY | MODE_RDWR))
|
||||
{
|
||||
this.outputStream =
|
||||
classes[FILEOUT_CTRID].createInstance(nsIFileOutputStream);
|
||||
this.outputStream.init(this.localFile, mode, perms, 0);
|
||||
}
|
||||
|
||||
if (mode & (MODE_RDONLY | MODE_RDWR))
|
||||
{
|
||||
var is = classes[FILEIN_CTRID].createInstance(nsIFileInputStream);
|
||||
is.init(this.localFile, mode, perms, tmp);
|
||||
this.inputStream =
|
||||
classes[SCRIPTSTREAM_CTRID].createInstance(nsIScriptableInputStream);
|
||||
this.inputStream.init(is);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LocalFile.prototype.write =
|
||||
function fo_write(buf)
|
||||
{
|
||||
if (!("outputStream" in this))
|
||||
throw "file not open for writing.";
|
||||
|
||||
return this.outputStream.write(buf, buf.length);
|
||||
}
|
||||
|
||||
LocalFile.prototype.read =
|
||||
function fo_read(max)
|
||||
{
|
||||
if (this.localFile.isDirectory())
|
||||
{
|
||||
var entries = this.localFile.directoryEntries;
|
||||
var array = [];
|
||||
while (entries.hasMoreElements())
|
||||
{
|
||||
var entry = entries.getNext();
|
||||
entry.QueryInterface(Components.interfaces.nsIFile);
|
||||
array.push(entry);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
if (!("inputStream" in this))
|
||||
throw "file not open for reading.";
|
||||
|
||||
var av = this.inputStream.available();
|
||||
if (typeof max == "undefined")
|
||||
max = av;
|
||||
|
||||
if (!av)
|
||||
return null;
|
||||
|
||||
var rv = this.inputStream.read(max);
|
||||
return rv;
|
||||
}
|
||||
|
||||
LocalFile.prototype.close =
|
||||
function fo_close()
|
||||
{
|
||||
if ("outputStream" in this)
|
||||
this.outputStream.close();
|
||||
if ("inputStream" in this)
|
||||
this.inputStream.close();
|
||||
}
|
||||
|
||||
LocalFile.prototype.flush =
|
||||
function fo_close()
|
||||
{
|
||||
return this.outputStream.flush();
|
||||
}
|
||||
//}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
261
content/find.js
Normal file
261
content/find.js
Normal file
@@ -0,0 +1,261 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
// TODO: proper backwards search - implement our own component?
|
||||
// : implement our own highlighter?
|
||||
// : frameset pages
|
||||
// : <ESC> should cancel search highlighting in 'incsearch' mode and jump
|
||||
// back to the presearch page location - can probably use the same
|
||||
// solution as marks
|
||||
// : 'linksearch' searches should highlight link matches only
|
||||
// : changing any search settings should also update the search state including highlighting
|
||||
// : incremental searches shouldn't permanently update search modifiers
|
||||
|
||||
// make sure you only create this object when the "vimperator" object is ready
|
||||
function Search() //{{{
|
||||
{
|
||||
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)
|
||||
var search_pattern = ""; // current search string (includes modifiers)
|
||||
var last_search_pattern = ""; // the last searched pattern (includes modifiers)
|
||||
var last_search_string = ""; // the last searched string (without modifiers)
|
||||
var last_search_backwards = false; // like "backwards", but for the last search, so if you cancel a search with <esc> this is not set
|
||||
var case_sensitive = false; // search string is case sensitive
|
||||
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(); });
|
||||
// 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(); });
|
||||
|
||||
// set search_string, search_pattern, case_sensitive, links_only
|
||||
function processUserPattern(pattern)
|
||||
{
|
||||
// strip off pattern terminator and offset
|
||||
if (backwards)
|
||||
pattern = pattern.replace(/\?.*/, "");
|
||||
else
|
||||
pattern = pattern.replace(/\/.*/, "");
|
||||
|
||||
search_pattern = pattern;
|
||||
|
||||
// links only search - \u wins if both modifiers specified
|
||||
if (/\\u/.test(pattern))
|
||||
links_only = false;
|
||||
else if (/\U/.test(pattern))
|
||||
links_only = true;
|
||||
else if (vimperator.options["linksearch"])
|
||||
links_only = true;
|
||||
else
|
||||
links_only = false;
|
||||
|
||||
// strip links-only modifiers
|
||||
pattern = pattern.replace(/(\\)?\\[uU]/g, function($0, $1) { return $1 ? $0 : "" });
|
||||
|
||||
// case sensitivity - \c wins if both modifiers specified
|
||||
if (/\c/.test(pattern))
|
||||
case_sensitive = false;
|
||||
else if (/\C/.test(pattern))
|
||||
case_sensitive = true;
|
||||
else if (vimperator.options["ignorecase"] && vimperator.options["smartcase"] && /[A-Z]/.test(pattern))
|
||||
case_sensitive = true;
|
||||
else if (vimperator.options["ignorecase"])
|
||||
case_sensitive = false;
|
||||
else
|
||||
case_sensitive = true;
|
||||
|
||||
// strip case-sensitive modifiers
|
||||
pattern = pattern.replace(/(\\)?\\[cC]/g, function($0, $1) { return $1 ? $0 : "" });
|
||||
|
||||
// remove any modifer escape \
|
||||
pattern = pattern.replace(/\\(\\[cCuU])/g, '$1')
|
||||
|
||||
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)
|
||||
{
|
||||
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 :(
|
||||
this.find = function(str, backwards)
|
||||
{
|
||||
var fastFind = getBrowser().fastFind;
|
||||
|
||||
processUserPattern(str);
|
||||
|
||||
fastFind.caseSensitive = case_sensitive;
|
||||
found = fastFind.find(search_string, links_only) != Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND;
|
||||
|
||||
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<esc> 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.echoerr("search hit TOP, continuing at BOTTOM");
|
||||
else
|
||||
vimperator.echoerr("search hit BOTTOM, continuing at TOP");
|
||||
}, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
vimperator.echo((up ? "?" : "/") + last_search_pattern);
|
||||
|
||||
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
|
||||
this.searchKeyPressed = function(command)
|
||||
{
|
||||
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
|
||||
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.set(vimperator.modes.NORMAL, null, true);
|
||||
}
|
||||
|
||||
// Called when the search is cancelled - for example if someone presses
|
||||
// escape while typing a search
|
||||
this.searchCanceled = function()
|
||||
{
|
||||
vimperator.modes.reset();
|
||||
//vimperator.focusContent();
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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"])
|
||||
}
|
||||
|
||||
this.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:
|
||||
145
content/help.css
Normal file
145
content/help.css
Normal file
@@ -0,0 +1,145 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
div.main {
|
||||
font-family: -moz-fixed;
|
||||
white-space: -moz-pre-wrap;
|
||||
width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p.tagline {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.vimperator {
|
||||
border-width: 1px;
|
||||
border-style: dotted;
|
||||
border-color: gray;
|
||||
/*margin-bottom: 2em; /* FIXME: just a quick hack until we have proper pages */
|
||||
}
|
||||
table.vimperator td {
|
||||
border: none;
|
||||
padding: 3px;
|
||||
}
|
||||
tr.separator {
|
||||
height: 10px;
|
||||
}
|
||||
hr {
|
||||
height: 1px;
|
||||
background-color: white;
|
||||
border-style: none;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
td.taglist {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
border-spacing: 13px 10px;
|
||||
}
|
||||
td.taglist td {
|
||||
width: 100px;
|
||||
padding: 3px 0px;
|
||||
}
|
||||
tr.taglist code, td.usage code {
|
||||
margin: 0px 2px;
|
||||
}
|
||||
td.usage code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
td.taglist code {
|
||||
margin-left: 2em;
|
||||
}
|
||||
code.tag {
|
||||
font-weight: bold;
|
||||
color: rgb(255, 0, 255); /* magenta */
|
||||
}
|
||||
tr.description {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
table.commands {
|
||||
background-color: rgb(250, 240, 230);
|
||||
}
|
||||
table.mappings {
|
||||
background-color: rgb(230, 240, 250);
|
||||
}
|
||||
table.options {
|
||||
background-color: rgb(240, 250, 230);
|
||||
}
|
||||
|
||||
fieldset.paypal {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.argument {
|
||||
color: #6A97D4;
|
||||
}
|
||||
|
||||
.command {
|
||||
font-weight: bold;
|
||||
color: #632610;
|
||||
}
|
||||
|
||||
.mapping {
|
||||
font-weight: bold;
|
||||
color: #102663;
|
||||
}
|
||||
|
||||
.option {
|
||||
font-weight: bold;
|
||||
color: #106326;
|
||||
}
|
||||
|
||||
.code {
|
||||
color: #108826;
|
||||
}
|
||||
|
||||
.shorthelp {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.version {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 2%;
|
||||
color: #C0C0C0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.warning {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
/* vim: set fdm=marker sw=4 ts=4 et: */
|
||||
292
content/help.js
Normal file
292
content/help.js
Normal file
@@ -0,0 +1,292 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
vimperator.help = function(section, easter) //{{{
|
||||
{
|
||||
if (easter)
|
||||
{
|
||||
vimperator.echoerr("E478: Don't panic!");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((arguments[3] && arguments[3].inTab))// || !window.content.document.open)
|
||||
vimperator.open("about:blank", vimperator.NEW_TAB);
|
||||
else
|
||||
vimperator.open("about:blank");
|
||||
|
||||
/* commands = array where help information is located
|
||||
* beg = string which is printed before the commmand/option/mapping name
|
||||
* end = string which is printed after the commmand/option/mapping name
|
||||
* func = called with 'command', result is a string is prepended to the help text
|
||||
*/
|
||||
function makeHelpString(commands, beg, end, func)
|
||||
{
|
||||
var ret = "";
|
||||
var separator = '<tr class="separator"><td colspan="3"><hr/></td></tr>\n';
|
||||
for (var command in commands)
|
||||
{
|
||||
// the usage information for the command
|
||||
ret += '<tr class="description"><td class="usage" valign="top">';
|
||||
for (var j=0; j < command.usage.length; j++)
|
||||
{
|
||||
var usage = command.usage[j];
|
||||
|
||||
// keep <br/>
|
||||
//usage = usage.replace(/<([^b][^r].*>)/g, "<$1");
|
||||
//usage = usage.replace(/[^b][^r][^\/]>/g, ">");
|
||||
usage = usage.replace(/&/g, "&");
|
||||
usage = usage.replace(/</g, "<");
|
||||
usage = usage.replace(/>/g, ">");
|
||||
usage = usage.replace(/\\n/g, "<br/>");
|
||||
// color [count], [!], {arg} and [arg] in the usage, not nice and error prone but the regexp work (for now)
|
||||
usage = usage.replace(/({[^}]+})/g, "<span class=\"argument\">$1</span>"); // required args
|
||||
usage = usage.replace(/(^|[^A-Za-z])(\[[^!\]]+\])/g, "$1<span class=\"argument\">$2</span>"); // optional args
|
||||
usage = usage.replace(/\[!\]/, "<span class=\"argument\">[!]</span>"); // special
|
||||
// and the 'option' in a different color
|
||||
usage = usage.replace(/^'(\w+)'/gm, "'<span class=\"option\">$1</span>'");
|
||||
ret += "<code>" + beg + usage + end + '</code><br/>';
|
||||
}
|
||||
ret += '</td><td valign="top">';
|
||||
|
||||
// the actual help text with the first line in bold
|
||||
if (command.short_help)
|
||||
{
|
||||
ret += '<span class="shorthelp">';
|
||||
ret += command.short_help; // the help description
|
||||
ret += "</span><br/>";
|
||||
if (func) // for options we print default values here, e.g.
|
||||
{
|
||||
ret += func.call(this, command);
|
||||
ret += "<br/>";
|
||||
}
|
||||
if (command.help)
|
||||
{
|
||||
ret += command.help; // the help description
|
||||
ret += "<br/>";
|
||||
}
|
||||
}
|
||||
else
|
||||
ret += "Sorry, no help available";
|
||||
// the tags which are printed on the top right
|
||||
ret += '</td><td class="taglist" valign="top">';
|
||||
var names = command.names;
|
||||
for (var j=0; j < names.length; j++)
|
||||
{
|
||||
var cmd_name = names[j];
|
||||
cmd_name = cmd_name.replace(/</g, "<");
|
||||
cmd_name = cmd_name.replace(/>/g, ">");
|
||||
// cmd_name = cmd_name.replace(/"/g, """);
|
||||
// cmd_name = cmd_name.replace(/'/g, "'");
|
||||
// cmd_name = cmd_name.replace(/&/g, "&");
|
||||
ret += '<code class="tag">' + beg + cmd_name + end + '</code><br/>';
|
||||
}
|
||||
ret += '</td></tr>';
|
||||
|
||||
// add more space between entries
|
||||
ret += separator;
|
||||
}
|
||||
ret = ret.replace(new RegExp(separator + '$'), ''); // FIXME: far too tasty!
|
||||
return ret;
|
||||
}
|
||||
|
||||
function makeOptionsHelpString(command)
|
||||
{
|
||||
var ret = "";
|
||||
ret = command.type + ' (default: ';
|
||||
if (command.type == "boolean")
|
||||
{
|
||||
if (command.default_value == true)
|
||||
ret += "on";
|
||||
else
|
||||
ret += "off";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeof command.default_value == 'string' && command.default_value.length == 0)
|
||||
ret += "''";
|
||||
else
|
||||
ret += command.default_value;
|
||||
}
|
||||
|
||||
ret += ")<br/>";
|
||||
return ret;
|
||||
}
|
||||
|
||||
var header = '<h1><img src="chrome://vimperator/content/logo_white.png" alt="Vimperator"></h1>' +
|
||||
'<p class="tagline">First there was a Navigator, then there was an Explorer.\n' +
|
||||
'Later it was time for a Konqueror. Now it\'s time for an Imperator, the VIMperator :)</p>';
|
||||
|
||||
var introduction = '<span style="float: right"><code class="tag">introduction</code></span><h2 id="introduction">Introduction</h2>' +
|
||||
'<p><a href="http://vimperator.mozdev.org">Vimperator</a> is a free browser add-on for Firefox, which makes it look and behave like the <a href="http://www.vim.org">Vim</a> text editor. ' +
|
||||
'It has similar key bindings, and you could call it a modal web browser, as key bindings differ according to which mode you are in.</p>' +
|
||||
|
||||
'<p><span class="warning">Warning:</span> To provide the most authentic Vim experience, the Firefox menubar and toolbar were hidden.<br/>' +
|
||||
'If you really need them, type: <code class="command">:set guioptions+=mT</code> to get them back.\n' +
|
||||
'If you don\'t like Vimperator at all, you can uninstall it by typing <code class="command">:addons</code> and remove/disable it.\n' +
|
||||
'If you like it, but can\'t remember the shortcuts, press <code class="mapping">F1</code> or <code class="command">:help</code> to get this help window back.</p>' +
|
||||
|
||||
'<p>Vimperator was written by <a href="mailto:stubenschrott@gmx.net">Martin Stubenschrott</a>. ' +
|
||||
'If you appreciate my work on Vimperator and want to encourage me working on it more, you can either send me greetings, patches or make a donation: </p>' +
|
||||
|
||||
'<form action="https://www.paypal.com/cgi-bin/webscr" method="post"><fieldset class="paypal">' +
|
||||
'<input type="hidden" name="cmd" value="_s-xclick"/>' +
|
||||
'<input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-but21.gif" name="submit" alt="Make payments with PayPal - it\'s fast, free and secure!"/>' +
|
||||
|
||||
'<img alt="" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1"/>' +
|
||||
'<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHPwYJKoZIhvcNAQcEoIIHMDCCBywCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBDDJfc+lXLBSAM9XSWv/ebzG/L7PTqYiIXaWVg8pfinDsfYaAcifcgCTuApg4v/VaZIQ/hLODzQu2EvmjGXP0twErA/Q8G5gx0l197PJSyVXb1sLwd1mgOdLF4t0HmDCdEI9z3H6CMhsb3xVwlfpzllSfCIqzlSpx4QtdzEZGzLDELMAkGBSsOAwIaBQAwgbwGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI8ZOwn5QkHgaAgZjjtPQxB7Vw2rS7Voap9y+xdVLoczUQ97hw+bOdZLcGykBtfoVjdn76MS51QKjGp1fEmxkqTuQ+Fxv8+OVtHu0QF/qlrhmC3fJBRJ0IFWxKdXS+Wod4615BDaG2X1hzvCL443ffka8XlLSiFTuW43BumQs/O+6Jqsk2hcReP3FIQOvtWMSgGTALnZx7x5c60u/3NSKW5qvyWKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA3MDMyMTIyMzI1OFowIwYJKoZIhvcNAQkEMRYEFCirrvlwYVHQiNEEbM6ikfx9+Dm5MA0GCSqGSIb3DQEBAQUABIGAtbsR8GdCdURLziozXLSdtY+zJZUPPeQFXXy2V1S/3ldiN+pRvd4HI7xz8mOY1UaKJZpwZnOosy9MflL1/hbiEtEyQ2Dm/s4jnTcJng/NjLIZu+0NYxXRJhB+zMJubnMMMjzNrGlqI4F2HAB/bCA1eOJ5B83Of3dA4rk/T/8GoSQ=-----END PKCS7-----"/>' +
|
||||
'</fieldset></form>' +
|
||||
|
||||
'<p>Of course as a believer in free open source software, only make a donation if you really like Vimperator and the money doesn\'t hurt - otherwise just use it, recommend it and like it :)</p>'
|
||||
|
||||
var initialization = '<span style="float: right"><code class="tag">initialization</code></span><h2 id="initialization">Initialization</h2>' +
|
||||
'<p>At startup Vimperator sources a user RC file, containing Ex commands, and any JavaScript files found in the plugin directory.</p>' +
|
||||
'<p>The RC file may be named .vimperatorrc or _vimperatorrc. The search order is:</p>' +
|
||||
'<ul>' +
|
||||
'<li>Unix and Mac - ~/.vimperatorrc then ~/_vimperatorrc</li>' +
|
||||
'<li>Windows - ~/_vimperatorrc then ~/.vimperatorrc</li>' +
|
||||
'</ul>' +
|
||||
'<p>The plugin directory is named:</p>' +
|
||||
'<ul>' +
|
||||
'<li>Unix and Mac - ~/.vimperator/plugin</li>' +
|
||||
'<li>Windows - ~/vimperator/plugin</li>' +
|
||||
'</ul>' +
|
||||
'<p>The user\'s $HOME(~) directory is determined as follows:</p>' +
|
||||
'<ul>' +
|
||||
'<li>Unix and Mac - $HOME is used.</li>' +
|
||||
'<li>Windows - if $HOME is set then this is used, otherwise $USERPROFILE or finally $HOMEDRIVE$HOMEPATH.</li>' +
|
||||
'</ul>';
|
||||
|
||||
var mappings = '<span style="float: right"><code class="tag">mappings</code></span><h2 id="mappings">Mappings</h2>' +
|
||||
'<p>The denotion of modifier keys is like in Vim, so C- means the Control key, M- the Meta key, A- the Alt key and S- the Shift key.</p>'+
|
||||
'<table class="vimperator mappings">'
|
||||
mappings += makeHelpString(vimperator.mappings, "", "", null);
|
||||
mappings += '</table>';
|
||||
if (section && section == 'holy-grail')
|
||||
mappings += '<div><p id="holy-grail">You found it, Arthur!</p></div>\n';
|
||||
|
||||
var commands = '<span style="float: right"><code class="tag">commands</code></span><h2 id="commands">Commands</h2>' +
|
||||
'<table class="vimperator commands">\n';
|
||||
commands += makeHelpString(vimperator.commands, ":", "", null);
|
||||
commands += '</table>';
|
||||
if (section && section == '42')
|
||||
commands += '<div><p id="42">What is the meaning of life, the universe and everything?<br/>' +
|
||||
'Douglas Adams, the only person who knew what this question really was about is<br/>' +
|
||||
'now dead, unfortunately. So now you might wonder what the meaning of death<br/>' +
|
||||
'is...</p></div>\n';
|
||||
|
||||
var options = '<span style="float: right"><code class="tag">options</code></span><h2 id="options">Options</h2>' +
|
||||
'<table class="vimperator options">\n';
|
||||
options += makeHelpString(vimperator.options, "'", "'", makeOptionsHelpString);
|
||||
options += '</table>';
|
||||
|
||||
var fulldoc = '<?xml version="1.0"?>\n' +
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
|
||||
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n<head>\n<title>Vimperator help</title>\n' +
|
||||
// XXX: stylesheet broken here? Have to add it in the vimperator.xul file
|
||||
'<link rel="stylesheet" href="chrome://vimperator/content/help.css" type="text/css"/>\n' +
|
||||
'</head>\n<body>\n<div class="main">\n' +
|
||||
'<span class="version">version ' + vimperator.version + '</span>\n' +
|
||||
header +
|
||||
introduction +
|
||||
initialization +
|
||||
mappings +
|
||||
commands +
|
||||
options +
|
||||
'\n</div>\n</body>\n</html>';
|
||||
|
||||
var doc = window.content.document;
|
||||
|
||||
try
|
||||
{
|
||||
doc.open();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
// when the url is "about:" or any other xhtml page the doc is not open
|
||||
// then retry again in 250ms but just once
|
||||
if (arguments[3] && arguments[3].recursive)
|
||||
return false;
|
||||
|
||||
vimperator.open("about:blank");
|
||||
setTimeout(function () { vimperator.help(section, false, null, {recursive: true}); }, 250);
|
||||
return;
|
||||
}
|
||||
doc.write(fulldoc);
|
||||
doc.close();
|
||||
|
||||
|
||||
// TODO: change to getBoundingClientRect() for FF 3.0
|
||||
function cumulativeOffset(element)
|
||||
{
|
||||
var valueT = 0, valueL = 0;
|
||||
if (!element)
|
||||
return [0, 0];
|
||||
|
||||
do
|
||||
{
|
||||
valueT += element.offsetTop || 0;
|
||||
valueL += element.offsetLeft || 0;
|
||||
element = element.offsetParent;
|
||||
}
|
||||
while (element);
|
||||
|
||||
return [valueL, valueT];
|
||||
}
|
||||
|
||||
if (section)
|
||||
{
|
||||
function findSectionElement(section)
|
||||
{
|
||||
return vimperator.buffer.evaluateXPath('//code[@class="tag" and text()="' + section + '"] | id("' + section + '")')
|
||||
.snapshotItem(0);
|
||||
}
|
||||
|
||||
var element = findSectionElement(section);
|
||||
if (!element)
|
||||
{
|
||||
var firstChar = section.charAt(0);
|
||||
if (firstChar != ':' && firstChar != "'")
|
||||
{
|
||||
element = findSectionElement(':' + section);
|
||||
if (!element)
|
||||
element = findSectionElement("'" + section + "'");
|
||||
}
|
||||
}
|
||||
if (!element)
|
||||
{
|
||||
vimperator.echoerr("E149: Sorry, no help for " + section);
|
||||
return;
|
||||
}
|
||||
// FIXME: H2 elements are currently wrapped in DIVs so this works
|
||||
var pos = cumulativeOffset(element.parentNode);
|
||||
// horizontal offset is annoying, set it to 0 (use pos[0] if you want horizontal offset)
|
||||
window.content.scrollTo(0, pos[1]);
|
||||
}
|
||||
} //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
873
content/hints.js
Normal file
873
content/hints.js
Normal file
@@ -0,0 +1,873 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
*
|
||||
* Mozilla Public License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License
|
||||
* Version 1.1 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License
|
||||
* at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS"
|
||||
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* The Original Code and Idea is the Hit-a-Hint Mozilla extension.
|
||||
* The Initial Developer of the Original Code and the Idea is Pekka
|
||||
* P. Sillanpaa. Portions created by Initial Developer are Copyright
|
||||
* (C) 2004. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s): Pekka Sillanpaa, Paul Stone
|
||||
* adapted for Vimperator use by: Martin Stubenschrott
|
||||
*
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
function Hints() //{{{
|
||||
{
|
||||
const HINT_PREFIX = 'hah_hint_'; // prefix for the hint id
|
||||
|
||||
this.hintedElements = function() { return hintedElems; };
|
||||
this.currentState = function() { return state;};
|
||||
this.setCurrentState = function(s) { state = s;};
|
||||
|
||||
var isHahModeEnabled = false; // is typing mode on
|
||||
var hintedElems = [];
|
||||
var linkNumString = ""; // the typed link number is in this string
|
||||
var linkCount = 0;
|
||||
var state = 0; // 0: empty or processing, 1: a full hint was parsed
|
||||
|
||||
var wins; // frame array
|
||||
|
||||
// each hint element is a clone of this element
|
||||
var hintElemSpan;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// hint activating and loading related functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function startCoordLoader(doc)
|
||||
{
|
||||
win = doc.defaultView;
|
||||
if (!win)
|
||||
return;
|
||||
|
||||
if (win.winId != null)
|
||||
{
|
||||
window.clearTimeout(win.coordLoaderId);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!wins)
|
||||
wins = [];
|
||||
|
||||
win.winId = wins.length;
|
||||
wins.push(win);
|
||||
}
|
||||
// logMessage("winId:"+win.winId);
|
||||
win.res = vimperator.buffer.evaluateXPath(vimperator.options["hinttags"], doc);
|
||||
win.coordLoaderId = window.setTimeout("vimperator.hints.loadCoord(" + win.winId + ", 0);", 1);
|
||||
}
|
||||
|
||||
this.loadCoord = function(winId, i)
|
||||
{
|
||||
win = wins[winId];
|
||||
|
||||
// win.res is not ready when loading has not finished yet
|
||||
if (!win.res)
|
||||
return;
|
||||
|
||||
var elem = win.res.snapshotItem(i);
|
||||
|
||||
if (elem)
|
||||
genElemCoords(elem);
|
||||
|
||||
i++;
|
||||
|
||||
if (i < win.res.snapshotLength && !isHahModeEnabled)
|
||||
window.setTimeout("vimperator.hints.loadCoord(" + winId + ", "+ i +");", 1);
|
||||
else
|
||||
win.coordLoaderId = null;
|
||||
};
|
||||
|
||||
function genElemCoords(elem)
|
||||
{
|
||||
// NOTE: experiment for making the function faster, report problems
|
||||
var rect = elem.getClientRects()[0];
|
||||
if (rect)
|
||||
{
|
||||
if (!rect.left || !rect.top)
|
||||
vimperator.log("HUI: no rect.left or top");
|
||||
elem.absoLeft = rect.left + window.content.scrollX;
|
||||
elem.absoTop = rect.top + window.content.scrollY;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function createHints(win)
|
||||
{
|
||||
if (!win)
|
||||
{
|
||||
win = window.content;
|
||||
linkCount = 0;
|
||||
}
|
||||
|
||||
var area = new Array(4);
|
||||
area[0] = win.pageXOffset - 5;
|
||||
area[1] = win.pageYOffset - 5;
|
||||
area[2] = area[0] + win.innerWidth;
|
||||
area[3] = area[1] + win.innerHeight;
|
||||
|
||||
var doc = win.document;
|
||||
var res = vimperator.buffer.evaluateXPath(vimperator.options["hinttags"], doc);
|
||||
|
||||
var elem, i;
|
||||
|
||||
hintElemSpan = doc.createElement('SPAN');
|
||||
hintElemSpan.style.cssText = vimperator.options["hintstyle"];
|
||||
hintElemSpan.setAttribute('name', 'hah_hint');
|
||||
|
||||
var hintContainer = doc.getElementById('hah_hints');
|
||||
if (hintContainer == null)
|
||||
{
|
||||
genHintContainer(doc);
|
||||
hintContainer = doc.getElementById('hah_hints');
|
||||
if (!hintContainer)
|
||||
return false;
|
||||
}
|
||||
hintContainer.valid_hint_count = 0; // none of these hints should be visible initially
|
||||
|
||||
var hints = hintContainer.childNodes;
|
||||
var maxhints = vimperator.options["maxhints"];
|
||||
//vimperator.log("snapshot length: " + res.snapshotLength);
|
||||
for (i = 0; i < res.snapshotLength; i++)
|
||||
{
|
||||
// this saves from script timeouts on pages with some thousand links
|
||||
if (linkCount >= maxhints)
|
||||
break;
|
||||
|
||||
elem = res.snapshotItem(i);
|
||||
genElemCoords(elem);
|
||||
|
||||
// for extended hint mode, show all - even currently hidden - hints
|
||||
//if (vimperator.hasMode(vimperator.modes.QUICK_HINT) && (elem.absoTop < area[1] || elem.absoTop > area[3] ||
|
||||
// if ((elem.absoTop < area[1] || elem.absoTop > area[3] ||
|
||||
// elem.absoLeft > area[2] || elem.absoLeft < area[0]))
|
||||
// continue;
|
||||
|
||||
// if (elem.offsetWidth == 0 && elem.offsetHeight == 0)
|
||||
// continue;
|
||||
|
||||
var cs = doc.defaultView.getComputedStyle(elem, null);
|
||||
|
||||
if (cs.getPropertyValue("visibility") == "hidden")
|
||||
continue;
|
||||
|
||||
if (linkCount < hints.length)
|
||||
hintElem = hints[linkCount];
|
||||
else // need to attach this new hintElem to the hint container
|
||||
{
|
||||
hintElem = hintElemSpan.cloneNode(false);
|
||||
hintContainer.appendChild(hintElem);
|
||||
}
|
||||
|
||||
hintElem.style.display = 'none';
|
||||
hintElem.style.top = elem.absoTop + "px";
|
||||
hintElem.style.left = elem.absoLeft + "px";
|
||||
hintElem.refElem = elem;
|
||||
|
||||
hintContainer.valid_hint_count++; // one more visible hint in this frame
|
||||
linkCount++; // and one more total hint
|
||||
|
||||
// process firefox event to keep the UI snappy
|
||||
// if (linkCount % 50 == 0)
|
||||
// {
|
||||
// Components.classes['@mozilla.org/thread-manager;1'].
|
||||
// getService().mainThread.processNextEvent(false);
|
||||
// //showHints(null, 0);
|
||||
// }
|
||||
}
|
||||
|
||||
doc.coordsInvalidated = false;
|
||||
|
||||
// recursively create hints
|
||||
for (i = 0; i < win.frames.length; i++)
|
||||
createHints(win.frames[i]);
|
||||
}
|
||||
|
||||
function showHints(win, off)
|
||||
{
|
||||
offset = off; // must be global without 'var' for recursion
|
||||
|
||||
if (!win)
|
||||
win = window.content;
|
||||
|
||||
if (linkCount == 0 && !(vimperator.modes.extended & vimperator.modes.ALWAYS_HINT))
|
||||
{
|
||||
vimperator.beep();
|
||||
vimperator.modes.reset();
|
||||
|
||||
// XXX: move to mode handling
|
||||
isHahModeEnabled = false;
|
||||
linkNumString = '';
|
||||
hintedElems = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var doc = win.document;
|
||||
var hintElem = null;
|
||||
var hintContainer = doc.getElementById('hah_hints');
|
||||
var hints = hintContainer.childNodes;
|
||||
var i, j;
|
||||
|
||||
for (i = 0; i < hintContainer.valid_hint_count; i++)
|
||||
{
|
||||
hintText = formatHint(offset+i);
|
||||
hintElem = hints[i];
|
||||
hintElem.style.display = 'inline';
|
||||
//hintElem.style.position = 'absolute';
|
||||
hintElem.innerHTML = hintText;
|
||||
hintElem.id = HINT_PREFIX + hintText;
|
||||
}
|
||||
offset += hintContainer.valid_hint_count;
|
||||
|
||||
// recursively show hints
|
||||
for (j = 0; j < win.frames.length; j++)
|
||||
showHints(win.frames[j], offset);
|
||||
}
|
||||
|
||||
/* removes all visible hints from doc
|
||||
* or from current document, if win == null
|
||||
*/
|
||||
function removeHints(win)
|
||||
{
|
||||
if (!win)
|
||||
win = window.content;
|
||||
|
||||
var doc = win.document;
|
||||
var res = vimperator.buffer.evaluateXPath("//HINTS/SPAN", doc)
|
||||
var elem, i;
|
||||
|
||||
for (i = 0; i < res.snapshotLength; i++)
|
||||
{
|
||||
elem = res.snapshotItem(i);
|
||||
setHintStyle(elem, vimperator.options["hintstyle"]);
|
||||
elem.style.display = 'none';
|
||||
}
|
||||
|
||||
for (i = 0; i < win.frames.length; i++)
|
||||
removeHints(win.frames[i]);
|
||||
}
|
||||
|
||||
function onResize(event)
|
||||
{
|
||||
if (event)
|
||||
doc = event.originalTarget;
|
||||
else
|
||||
doc = window.content.document;
|
||||
|
||||
invalidateCoords(doc);
|
||||
startCoordLoader(doc);
|
||||
}
|
||||
|
||||
function invalidateCoords(doc)
|
||||
{
|
||||
if (!doc.coordsInvalidated)
|
||||
{
|
||||
// every element has .validCoord
|
||||
// if it is the same as doc:s .validCoords,
|
||||
// the coordinates have not been regenerated, otherwise they
|
||||
// have. This way we can also use recursive generation
|
||||
// so that the coordinates are generated for every
|
||||
// element just once
|
||||
doc.validCoords = !doc.validCoords;
|
||||
// this is because window can be resized many times
|
||||
// and the coords should be invalidated just once.
|
||||
doc.coordsInvalidated = true;
|
||||
// logMessage(doc.validCoords);
|
||||
}
|
||||
}
|
||||
|
||||
function getHintById(id, win)
|
||||
{
|
||||
if (!win)
|
||||
win = window.content;
|
||||
|
||||
var doc = win.document;
|
||||
var elem, i;
|
||||
|
||||
//var hintId = parseInt(id, nums.length);
|
||||
//elem = doc.getElementById(prefix + hintId);
|
||||
elem = doc.getElementById(HINT_PREFIX + id);
|
||||
|
||||
if (elem)
|
||||
{
|
||||
return elem;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < win.frames.length; i++)
|
||||
{
|
||||
elem = getHintById(id, win.frames[i]);
|
||||
if (elem)
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatHint(hintNum)
|
||||
{
|
||||
var hintCharacters = vimperator.options["hintchars"];
|
||||
var str = hintNum.toString(hintCharacters.length); // turn hintNum into a base(length) number
|
||||
|
||||
// map the number onto the chars in the numbers string
|
||||
var result = '';
|
||||
// make all links the same length
|
||||
var hintLength = 1;
|
||||
var tmp = linkCount;
|
||||
while ((tmp /= hintCharacters.length) > 1.0)
|
||||
hintLength++;
|
||||
while (str.length < hintLength)
|
||||
{
|
||||
result += hintCharacters.charAt(0).toUpperCase();
|
||||
hintLength--;
|
||||
}
|
||||
|
||||
for (var i = 0; i < str.length; i++)
|
||||
result += (hintCharacters.charAt(parseInt(str[i], hintCharacters.length))).toUpperCase();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function setHintStyle(hintElem, styleString)
|
||||
{
|
||||
if (hintElem && hintElem.style)
|
||||
{
|
||||
xTemp = hintElem.style.left;
|
||||
yTemp = hintElem.style.top;
|
||||
hintElem.style.cssText = styleString;
|
||||
hintElem.style.left = xTemp;
|
||||
hintElem.style.top = yTemp;
|
||||
}
|
||||
}
|
||||
|
||||
function changeHintFocus(linkNumString, oldLinkNumString)
|
||||
{
|
||||
var styleString = vimperator.options["hintstyle"];
|
||||
var styleStringFocus = vimperator.options["focusedhintstyle"];
|
||||
var hintElem;
|
||||
|
||||
if (oldLinkNumString.length > 0)
|
||||
{
|
||||
hintElem = getHintById(oldLinkNumString);
|
||||
setHintStyle(hintElem, styleString);
|
||||
}
|
||||
if (linkNumString.length > 0)
|
||||
{
|
||||
hintElem = getHintById(linkNumString);
|
||||
setHintStyle(hintElem, styleStringFocus);
|
||||
if (hintElem)
|
||||
setMouseOverElement(hintElem.refElem);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// basic functionality
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Enables the HaH-mode by showing the hints and prepare to input the
|
||||
* hint numbers
|
||||
*
|
||||
* @author Pekka Sillanpaa
|
||||
* @param event that caused the mode to change
|
||||
* @return -1 if already enabled
|
||||
*/
|
||||
// XXX: move to mode handling
|
||||
this.enableHahMode = function(mode)
|
||||
{
|
||||
vimperator.modes.set(vimperator.modes.HINTS, mode);
|
||||
state = 0;
|
||||
linkCount = 0;
|
||||
linkNumString = '';
|
||||
isHahModeEnabled = true;
|
||||
|
||||
createHints();
|
||||
showHints(null, 0);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disables the HaH-mode by hiding the hints and disabling the input mode
|
||||
*
|
||||
* @author Pekka Sillanpaa
|
||||
* @param event that caused the mode to change
|
||||
* @param action = true if something is to be clicked
|
||||
* false if cancel
|
||||
* @return -1 if already disabled
|
||||
*/
|
||||
// XXX: move to mode handling
|
||||
this.disableHahMode = function(win)
|
||||
{
|
||||
if (!isHahModeEnabled)
|
||||
return;
|
||||
|
||||
vimperator.modes.reset();
|
||||
|
||||
isHahModeEnabled = false;
|
||||
linkNumString = '';
|
||||
hintedElems = [];
|
||||
|
||||
removeHints(win);
|
||||
return 0;
|
||||
};
|
||||
|
||||
this.resetHintedElements = function()
|
||||
{
|
||||
linkNumString = '';
|
||||
state = 0;
|
||||
|
||||
while (hintedElems.length > 0)
|
||||
{
|
||||
var elem = hintedElems.pop();
|
||||
if (!elem)
|
||||
return 0;
|
||||
// reset style attribute
|
||||
setHintStyle(elem, vimperator.options["hintstyle"]);
|
||||
}
|
||||
};
|
||||
|
||||
this.reshowHints = function()
|
||||
{
|
||||
onResize(null);
|
||||
|
||||
if (isHahModeEnabled)
|
||||
{
|
||||
removeHints();
|
||||
createHints();
|
||||
showHints(null, 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// this function 'click' an element, which also works
|
||||
// for javascript links
|
||||
this.openHints = function(new_tab, new_window)
|
||||
{
|
||||
var x = 0, y = 0;
|
||||
|
||||
while (hintedElems.length > 0)
|
||||
{
|
||||
var elem = hintedElems.pop();
|
||||
if (!elem)
|
||||
return 0;
|
||||
|
||||
setHintStyle(elem, vimperator.options["hintstyle"]);
|
||||
elem = elem.refElem;
|
||||
var elemTagName = elem.tagName;
|
||||
elem.focus();
|
||||
|
||||
if (elemTagName == 'FRAME' || elemTagName == 'IFRAME')
|
||||
return 0;
|
||||
|
||||
// for imagemap
|
||||
if (elemTagName == 'AREA')
|
||||
{
|
||||
var coords = elem.getAttribute("coords").split(",");
|
||||
x = Number(coords[0]);
|
||||
y = Number(coords[1]);
|
||||
}
|
||||
doc = window.content.document;
|
||||
view = window.document.defaultView;
|
||||
|
||||
var evt = doc.createEvent('MouseEvents');
|
||||
evt.initMouseEvent('mousedown', true, true, view, 1, x+1, y+1, 0, 0, /*ctrl*/ new_tab, /*event.altKey*/0, /*event.shiftKey*/ new_window, /*event.metaKey*/ new_tab, 0, null);
|
||||
elem.dispatchEvent(evt);
|
||||
|
||||
var evt = doc.createEvent('MouseEvents');
|
||||
evt.initMouseEvent('click', true, true, view, 1, x+1, y+1, 0, 0, /*ctrl*/ new_tab, /*event.altKey*/0, /*event.shiftKey*/ new_window, /*event.metaKey*/ new_tab, 0, null);
|
||||
elem.dispatchEvent(evt);
|
||||
|
||||
// for 'pure' open calls without a new tab or window it doesn't
|
||||
// make sense to open more hints in the current tab, open new tabs
|
||||
// for it
|
||||
if (!new_tab && !new_window)
|
||||
new_tab = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
this.yankUrlHints = function()
|
||||
{
|
||||
var loc = "";
|
||||
var elems = this.hintedElements();
|
||||
var tmp = "";
|
||||
for (var i = 0; i < elems.length; i++)
|
||||
{
|
||||
tmp = elems[i].refElem.href;
|
||||
if (typeof(tmp) != 'undefined' && tmp.length > 0)
|
||||
{
|
||||
if (i > 0)
|
||||
loc += "\n";
|
||||
loc += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// disable the hints before we can echo() an information
|
||||
this.disableHahMode(null, true);
|
||||
|
||||
vimperator.copyToClipboard(loc);
|
||||
vimperator.echo("Yanked " + loc);
|
||||
};
|
||||
|
||||
this.yankTextHints = function()
|
||||
{
|
||||
var loc = "";
|
||||
var elems = this.hintedElements();
|
||||
var tmp = "";
|
||||
for (var i = 0; i < elems.length; i++)
|
||||
{
|
||||
tmp = elems[i].refElem.textContent;
|
||||
if (typeof(tmp) != 'undefined' && tmp.length > 0)
|
||||
{
|
||||
if (i > 0)
|
||||
loc += "\n";
|
||||
loc += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// disable the hints before we can echo() an information
|
||||
this.disableHahMode(null, true);
|
||||
|
||||
vimperator.copyToClipboard(loc);
|
||||
vimperator.echo("Yanked " + loc);
|
||||
};
|
||||
|
||||
function setMouseOverElement(elem)
|
||||
{
|
||||
var doc = window.document;
|
||||
|
||||
if (elem.tagName == 'FRAME' || elem.tagName == 'IFRAME')
|
||||
{
|
||||
elem.contentWindow.focus();
|
||||
return;
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// elem.focus();
|
||||
//}
|
||||
|
||||
var evt = doc.createEvent('MouseEvents');
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
// for imagemap
|
||||
if (elem.tagName == 'AREA')
|
||||
{
|
||||
var coords = elem.getAttribute("coords").split(",");
|
||||
x = Number(coords[0]);
|
||||
y = Number(coords[1]);
|
||||
}
|
||||
|
||||
evt.initMouseEvent('mouseover', true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
|
||||
elem.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// event handlers
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// returns nr. of fully parsed links when a new hint has been found,
|
||||
// otherwise 0 if current state is part of a hint, or -1 if an error occured
|
||||
// (like we have typed keys which never can become a hint
|
||||
this.processEvent = function(event)
|
||||
{
|
||||
if (!isHahModeEnabled)
|
||||
return -1;
|
||||
|
||||
// reset state to show that we are in processing mode
|
||||
state = 0;
|
||||
|
||||
var num = String.fromCharCode(event.charCode).toUpperCase();
|
||||
var hintCharacters = vimperator.options["hintchars"];
|
||||
if (num != null && hintCharacters.toUpperCase().indexOf(num) > -1)
|
||||
{
|
||||
var oldLinkNumString = linkNumString;
|
||||
linkNumString += '' + num;
|
||||
// update reference to currently selected node;
|
||||
var elem = getHintById(linkNumString);
|
||||
changeHintFocus(linkNumString, oldLinkNumString);
|
||||
|
||||
// if we found the hint, fine just return it
|
||||
if (elem)
|
||||
{
|
||||
hintedElems.push(elem);
|
||||
linkNumString = '';
|
||||
state = 1;
|
||||
return hintedElems.length;
|
||||
}
|
||||
|
||||
//calculate how many characters a hint must have
|
||||
var hintLength = 1;
|
||||
var tmp = linkCount;
|
||||
while ((tmp /= hintCharacters.length) > 1.0)
|
||||
hintLength++;
|
||||
|
||||
if (linkNumString.length >= hintLength)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
// an unparseable or wrong key
|
||||
return -1;
|
||||
}
|
||||
|
||||
function genHintContainer(doc)
|
||||
{
|
||||
if (doc.getElementsByTagName('HINTS').length > 0)
|
||||
return;
|
||||
|
||||
hints = doc.createElement('HINTS');
|
||||
hints.id = "hah_hints";
|
||||
hints.valid_hint_count = 0; // initially 0 elements are usable as hints
|
||||
|
||||
if (doc.body)
|
||||
doc.body.appendChild(hints);
|
||||
}
|
||||
|
||||
function initDoc(event)
|
||||
{
|
||||
// vimperator.echoerr("Content loaded");
|
||||
|
||||
doc = event.originalTarget;
|
||||
genHintContainer(doc);
|
||||
isHahModeEnabled = false;
|
||||
hintedElems = [];
|
||||
|
||||
if (!doc.validCoords)
|
||||
doc.validCoords = true;
|
||||
else
|
||||
doc.validCoords = false;
|
||||
|
||||
// XXX: prepend a ! ?
|
||||
if (doc.coordsInvalidated)
|
||||
doc.coordsInvalidated = true;
|
||||
else
|
||||
doc.coordsInvalidated = false;
|
||||
|
||||
startCoordLoader(doc);
|
||||
|
||||
if (vimperator.modes.extended & vimperator.modes.ALWAYS_HINT)
|
||||
{
|
||||
state = 0;
|
||||
linkCount = 0;
|
||||
linkNumString = '';
|
||||
isHahModeEnabled = true;
|
||||
|
||||
setTimeout( function() {
|
||||
createHints();
|
||||
showHints(null, 0);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// window.document.addEventListener("pageshow", function() { vimperator.log("pageshow"); }, null);
|
||||
// FIXME: add resize support
|
||||
//window.addEventListener("resize", onResize, null);
|
||||
|
||||
getBrowser().addEventListener("DOMContentLoaded", function(event) {
|
||||
if (vimperator.options["autohints"])
|
||||
vimperator.hints.show(event.target);
|
||||
}, false);
|
||||
|
||||
this.show = function(doc, takenHints)
|
||||
{
|
||||
function getNextHintText(href)
|
||||
{
|
||||
var hintCharacters = "abcdefghijklmnopqrstuvwxyz123456789"; // no 0, as it looks too much like O
|
||||
var len = hintCharacters.length;
|
||||
var text = "aa";
|
||||
for (; nextHintFirstChar < len; nextHintFirstChar++)
|
||||
{
|
||||
for (; nextHintSecondChar < len; nextHintSecondChar++)
|
||||
{
|
||||
text = hintCharacters[nextHintFirstChar] + hintCharacters[nextHintSecondChar];
|
||||
if (typeof takenHints[text] === "undefined")
|
||||
{
|
||||
takenHints[text] = href;
|
||||
return text.toUpperCase();
|
||||
}
|
||||
}
|
||||
nextHintSecondChar = 0;
|
||||
}
|
||||
vimperator.log("Too many hints on page");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!doc)
|
||||
doc = window.content.document;
|
||||
if (!takenHints)
|
||||
takenHints = {};
|
||||
|
||||
var rel = 0, abs = 0, inl = 0, disc = 0;
|
||||
var nextHintFirstChar = 0, nextHintSecondChar = 0;
|
||||
|
||||
var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"]
|
||||
.createInstance()
|
||||
.QueryInterface(Components.interfaces.nsIFind);
|
||||
finder.caseSensitive = false;
|
||||
|
||||
var baseNodeInline = doc.createElementNS("http://www.w3.org/1999/xhtml", "span");
|
||||
baseNodeInline.style.backgroundColor = "#BCEE68";
|
||||
baseNodeInline.style.color = "black";
|
||||
baseNodeInline.style.display = "inline";
|
||||
baseNodeInline.style.fontSize = "inherit";
|
||||
baseNodeInline.style.padding = "0px";
|
||||
baseNodeInline.className = "vimperator-hint-inline";
|
||||
var baseNodeAbsolute = doc.createElementNS("http://www.w3.org/1999/xhtml", "span");
|
||||
//baseNodeAbsolute.style.backgroundColor = "#BCEE68";
|
||||
baseNodeAbsolute.style.backgroundColor = "cyan";
|
||||
baseNodeAbsolute.style.color = "black";
|
||||
baseNodeAbsolute.style.position = "absolute";
|
||||
baseNodeAbsolute.style.fontSize = "10px";
|
||||
baseNodeAbsolute.style.fontWeight = "bold";
|
||||
baseNodeAbsolute.style.lineHeight = "10px";
|
||||
baseNodeAbsolute.style.padding = "0px 1px 0px 0px";
|
||||
baseNodeAbsolute.style.zIndex = "5000";
|
||||
baseNodeAbsolute.className = "vimperator-hint-absolute";
|
||||
|
||||
var scrollX = doc.defaultView.scrollX;
|
||||
var scrollY = doc.defaultView.scrollY;
|
||||
//var view = doc.defaultView;
|
||||
|
||||
var retRange = null;
|
||||
var searchRange = doc.createRange();
|
||||
var res = vimperator.buffer.evaluateXPath(vimperator.options["hinttags"], doc, null, true);
|
||||
var word, elem, tagname, count, href, text, lowertext;
|
||||
vimperator.log("Hinting " + res.snapshotLength + " items on " + doc.title);
|
||||
outer:
|
||||
for (var i = 0; i < res.snapshotLength; i++)
|
||||
{
|
||||
// the more often we check for firefox events, the slower it is
|
||||
// best is checking between every 50-500 elements
|
||||
if (i % 200 == 0)
|
||||
{
|
||||
Components.classes['@mozilla.org/thread-manager;1'].
|
||||
getService().mainThread.processNextEvent(false);
|
||||
|
||||
// update saved positions, as the user could have scrolled
|
||||
scrollX = doc.defaultView.scrollX;
|
||||
scrollY = doc.defaultView.scrollY;
|
||||
vimperator.log(scrollY);
|
||||
}
|
||||
|
||||
elem = res.snapshotItem(i);
|
||||
tagname = elem.tagName.toLowerCase();
|
||||
|
||||
|
||||
//if (vimperator.buffer.evaluateXPath(".//*[@class='vimperator-hint-inline']", doc, elem).snapshotLength > 0 )
|
||||
// continue outer;
|
||||
if (elem.getElementsByClassName("vimperator-hint-inline").length > 0)
|
||||
continue outer;
|
||||
|
||||
count = elem.childNodes.length;
|
||||
searchRange.setStart(elem, 0);
|
||||
searchRange.setEnd(elem, count);
|
||||
|
||||
// try to get a unique substring of the element
|
||||
if (tagname == "input" || tagname == "textarea" || tagname == "select")
|
||||
text = "";
|
||||
else
|
||||
text = elem.textContent; // faster than searchRange.toString()
|
||||
|
||||
href = elem.getAttribute("href");
|
||||
for (var j = 0; j < text.length - 1; j++)
|
||||
{
|
||||
if (text.length < 2)
|
||||
continue;
|
||||
|
||||
word = text.substr(j, 2);
|
||||
lowertext = word.toLowerCase();
|
||||
if (/[^a-z0-9]/.test(lowertext)) // 2x as fast as lowertext[0] > "a" etc. testing
|
||||
continue;
|
||||
|
||||
//dump(elem.tagname + " - " + text + ": " + word + "\n");
|
||||
|
||||
// hint not yet taken or taken and href the same
|
||||
if (typeof(takenHints[lowertext]) === "undefined" || (href && takenHints[lowertext] == href))
|
||||
{
|
||||
takenHints[lowertext] = href;
|
||||
inl++;
|
||||
|
||||
retRange = finder.Find(word, searchRange, searchRange, searchRange);
|
||||
if (!retRange)
|
||||
continue;
|
||||
|
||||
var nodeSurround = baseNodeInline.cloneNode(true);
|
||||
var startContainer = retRange.startContainer;
|
||||
var startOffset = retRange.startOffset;
|
||||
var docfrag = retRange.extractContents();
|
||||
var before = startContainer.splitText(startOffset);
|
||||
var parent = before.parentNode;
|
||||
nodeSurround.appendChild(docfrag);
|
||||
parent.insertBefore(nodeSurround, before);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
// if we came here, there was no suitable inline hint, need
|
||||
// to create an span relatively positioned to the element
|
||||
if (tagname != "input" && tagname != "textarea" && tagname != "select")
|
||||
{
|
||||
// heuristics to avoid duplicate relative hints for the same things:
|
||||
// 1. a link with the same href in one of the next two elements
|
||||
// this often is true for images which have a textlink right to it
|
||||
// or a menu made up of a <td>
|
||||
if (i < res.snapshotLength-2)
|
||||
{
|
||||
if (href && href == res.snapshotItem(i+1).getAttribute("href") ||
|
||||
href == res.snapshotItem(i+2).getAttribute("href"))
|
||||
{
|
||||
disc++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
elem.style.position = "relative";
|
||||
rel++;
|
||||
var span = doc.createElement("span");
|
||||
span.setAttribute("style", "z-index: 5000; color:black; font-weight: bold; font-size: 10px; background-color:yellow; line-height: 10px; border: 0px; padding: 0px 1px 0px 0px; position: absolute; left: 0px; top: 0px");
|
||||
var hint = getNextHintText(href);
|
||||
if (!hint)
|
||||
return false;
|
||||
span.innerHTML = hint;
|
||||
//setTimeout(function() { elem.appendChild(span); }, 10); // 10ms delay to let firefox handle position=relative
|
||||
elem.appendChild(span);
|
||||
continue;
|
||||
}
|
||||
else // absolute positioning
|
||||
{
|
||||
var rect = elem.getClientRects()[0];
|
||||
if (rect)
|
||||
{
|
||||
var span = baseNodeAbsolute.cloneNode(true);
|
||||
var hint = getNextHintText(href);
|
||||
if (!hint)
|
||||
return false;
|
||||
span.innerHTML = hint;
|
||||
span.style.left = rect.left + scrollX + "px";
|
||||
span.style.top = rect.top + scrollY + "px";
|
||||
doc.body.appendChild(span);
|
||||
abs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
vimperator.log("Hinted " + res.snapshotLength + " items on " + doc.title + " - inl: " + inl+ " rel: " + rel + " abs: " + abs + " discard: " + disc, 7);
|
||||
return true;
|
||||
}
|
||||
|
||||
} //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
BIN
content/logo_white.png
Normal file
BIN
content/logo_white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
1898
content/mappings.js
Normal file
1898
content/mappings.js
Normal file
File diff suppressed because it is too large
Load Diff
215
content/modes.js
Normal file
215
content/modes.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
vimperator.modes = (function()
|
||||
{
|
||||
var main = 1; // NORMAL
|
||||
var extended = 0; // NONE
|
||||
|
||||
var passNextKey = false;
|
||||
var passAllKeys = false;
|
||||
|
||||
function getModeMessage()
|
||||
{
|
||||
if (passNextKey && !passAllKeys)
|
||||
return "PASS THROUGH (next)";
|
||||
else if (passAllKeys && !passNextKey)
|
||||
return "PASS THROUGH";
|
||||
|
||||
var ext = "";
|
||||
switch (extended)
|
||||
{
|
||||
case vimperator.modes.QUICK_HINT:
|
||||
ext = " (quick)"; break;
|
||||
case vimperator.modes.EXTENDED_HINT:
|
||||
ext = " (extended)"; break;
|
||||
case vimperator.modes.ALWAYS_HINT:
|
||||
ext = " (always)"; break;
|
||||
case vimperator.modes.MENU: // TODO: desirable?
|
||||
ext = " (menu)"; break;
|
||||
}
|
||||
|
||||
switch (main)
|
||||
{
|
||||
case vimperator.modes.INSERT:
|
||||
return "INSERT" + ext;
|
||||
case vimperator.modes.VISUAL:
|
||||
return (extended & vimperator.modes.LINE) ? "VISUAL LINE" + ext : "VISUAL" + ext;
|
||||
case vimperator.modes.HINTS:
|
||||
return "HINTS" + ext;
|
||||
case vimperator.modes.CARET:
|
||||
return "CARET" + ext;
|
||||
case vimperator.modes.TEXTAREA:
|
||||
return "TEXTAREA" + ext;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleModeChange(oldmode, newmode)
|
||||
{
|
||||
vimperator.log("switching from mode " + oldmode + " to mode " + newmode, 7);
|
||||
|
||||
switch (oldmode)
|
||||
{
|
||||
case vimperator.modes.TEXTAREA:
|
||||
case vimperator.modes.INSERT:
|
||||
vimperator.editor.unselectText();
|
||||
break;
|
||||
|
||||
case vimperator.modes.VISUAL:
|
||||
if (newmode == vimperator.modes.CARET)
|
||||
{
|
||||
// clear any selection made
|
||||
var selection = window.content.getSelection();
|
||||
try { // a simple if (selection) does not work
|
||||
selection.collapseToStart();
|
||||
} catch (e) { }
|
||||
}
|
||||
else
|
||||
vimperator.editor.unselectText();
|
||||
break;
|
||||
|
||||
case vimperator.modes.HINTS:
|
||||
// XXX: for now this does not work, but later it should be here
|
||||
// vimperator.hints.disableHahMode();
|
||||
break;
|
||||
}
|
||||
|
||||
if (newmode == vimperator.modes.NORMAL)
|
||||
{
|
||||
var value = Options.getFirefoxPref("accessibility.browsewithcaret", false);
|
||||
if (value)
|
||||
Options.setFirefoxPref("accessibility.browsewithcaret", false);
|
||||
|
||||
vimperator.statusline.updateUrl();
|
||||
vimperator.focusContent();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// main modes, only one should ever be active
|
||||
NONE: 0,
|
||||
NORMAL: 1 << 0,
|
||||
INSERT: 1 << 1,
|
||||
VISUAL: 1 << 2,
|
||||
HINTS: 1 << 3,
|
||||
COMMAND_LINE: 1 << 4,
|
||||
CARET: 1 << 5, // text cursor is visible
|
||||
TEXTAREA: 1 << 6, // text cursor is in a HTMLTextAreaElement
|
||||
// extended modes, can include multiple modes, and even main modes
|
||||
EX: 1 << 10,
|
||||
INPUT_MULTILINE: 1 << 11,
|
||||
OUTPUT_MULTILINE: 1 << 12,
|
||||
SEARCH_FORWARD: 1 << 13,
|
||||
SEARCH_BACKWARD: 1 << 14,
|
||||
QUICK_HINT: 1 << 15,
|
||||
EXTENDED_HINT: 1 << 16,
|
||||
ALWAYS_HINT: 1 << 17,
|
||||
MENU: 1 << 18, // a popupmenu is active
|
||||
LINE: 1 << 19, // linewise visual mode
|
||||
|
||||
reset: function(silent)
|
||||
{
|
||||
this.set(vimperator.modes.NORMAL, vimperator.modes.NONE, silent);
|
||||
},
|
||||
|
||||
show: function()
|
||||
{
|
||||
if (!vimperator.options["showmode"])
|
||||
return;
|
||||
|
||||
// never show mode messages if we are in command line mode
|
||||
if (main == vimperator.modes.COMMAND_LINE)
|
||||
return;
|
||||
|
||||
var msg = getModeMessage();
|
||||
if (msg)
|
||||
vimperator.commandline.echo("-- " + getModeMessage() + " --");
|
||||
else
|
||||
vimperator.commandline.echo("");
|
||||
},
|
||||
|
||||
// helper function to set both modes in one go
|
||||
set: function(main_mode, extended_mode, silent)
|
||||
{
|
||||
// if a main mode is set, the extended is always cleared
|
||||
if (typeof main_mode === "number")
|
||||
{
|
||||
if (main_mode != main)
|
||||
handleModeChange(main, main_mode);
|
||||
|
||||
main = main_mode;
|
||||
if (!extended_mode)
|
||||
extended = vimperator.modes.NONE;
|
||||
|
||||
}
|
||||
if (typeof extended_mode === "number")
|
||||
extended = extended_mode;
|
||||
|
||||
if (!silent)
|
||||
this.show();
|
||||
},
|
||||
|
||||
// add/remove always work on the extended mode only
|
||||
add: function(mode)
|
||||
{
|
||||
extended |= mode;
|
||||
this.show();
|
||||
},
|
||||
remove: function(mode)
|
||||
{
|
||||
extended = (extended | mode) ^ mode;
|
||||
this.show();
|
||||
},
|
||||
|
||||
get passNextKey() { return passNextKey; },
|
||||
set passNextKey(value) { passNextKey = value; this.show(); },
|
||||
|
||||
get passAllKeys() { return passAllKeys; },
|
||||
set passAllKeys(value) { passAllKeys = value; this.show(); },
|
||||
|
||||
get main() { return main; },
|
||||
set main(value) {
|
||||
if (value != main)
|
||||
handleModeChange(main, value);
|
||||
|
||||
main = value;
|
||||
// setting the main mode always resets any extended mode
|
||||
extended = vimperator.modes.NONE;
|
||||
this.show();
|
||||
},
|
||||
|
||||
get extended() { return extended; },
|
||||
set extended(value) {
|
||||
extended = value; this.show();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
659
content/options.js
Normal file
659
content/options.js
Normal file
@@ -0,0 +1,659 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
function Option(names, type, extra_info) //{{{
|
||||
{
|
||||
if (!names || !type)
|
||||
return null;
|
||||
|
||||
this.name = names[0];
|
||||
this.names = names;
|
||||
this.usage = this.names;
|
||||
this.type = type;
|
||||
|
||||
this.setter = function(value) { Options.setPref(this.name, value); };
|
||||
this.getter = function() { return Options.getPref(this.name); };
|
||||
|
||||
if (extra_info)
|
||||
{
|
||||
if (extra_info.usage)
|
||||
this.usage = extra_info.usage;
|
||||
|
||||
this.help = extra_info.help || null;
|
||||
this.short_help = extra_info.short_help || null;
|
||||
|
||||
// "", 0 are valid default values
|
||||
if (extra_info.default_value !== undefined)
|
||||
this.default_value = extra_info.default_value;
|
||||
else
|
||||
this.default_value = null;
|
||||
|
||||
if (extra_info.setter)
|
||||
this.setter = extra_info.setter;
|
||||
if (extra_info.getter)
|
||||
this.getter = extra_info.getter;
|
||||
|
||||
this.completer = extra_info.completer || null;
|
||||
this.validator = extra_info.validator || null;
|
||||
}
|
||||
|
||||
// add noOPTION variant of boolean OPTION to this.names
|
||||
// FIXME: are these variants really considered names?
|
||||
if (this.type == "boolean")
|
||||
{
|
||||
this.names = []; // reset since order is important
|
||||
for (var i = 0; i < names.length; i++)
|
||||
{
|
||||
this.names.push(names[i]);
|
||||
this.names.push("no" + names[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: forced defaults need to use Options.getPref
|
||||
Option.prototype.__defineGetter__("value", function() { return this.getter.call(this); });
|
||||
Option.prototype.__defineSetter__("value", function(value) { this.setter.call(this, value); });
|
||||
|
||||
// TODO: add is[Type]() queries for use in set()?
|
||||
// : add isValid() or just throw an exception?
|
||||
|
||||
this.hasName = function(name)
|
||||
{
|
||||
for (var i = 0; i < this.names.length; i++)
|
||||
{
|
||||
if (this.names[i] == name)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} //}}}
|
||||
|
||||
function Options() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var firefox_prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
var vimperator_prefs = firefox_prefs.getBranch("extensions.vimperator.");
|
||||
var options = [];
|
||||
|
||||
function addOption(option)
|
||||
{
|
||||
Options.prototype.__defineGetter__(option.name, function() { return option.value; });
|
||||
Options.prototype.__defineSetter__(option.name, function(value) { option.value = value; });
|
||||
options.push(option);
|
||||
}
|
||||
|
||||
function optionsIterator()
|
||||
{
|
||||
for (var i = 0; i < options.length; i++)
|
||||
yield options[i];
|
||||
|
||||
throw StopIteration;
|
||||
}
|
||||
|
||||
function storePreference(name, value, vimperator_branch)
|
||||
{
|
||||
if (vimperator_branch)
|
||||
branch = vimperator_prefs;
|
||||
else
|
||||
branch = firefox_prefs;
|
||||
|
||||
switch (typeof value)
|
||||
{
|
||||
case "string":
|
||||
branch.setCharPref(name, value);
|
||||
break;
|
||||
case "number":
|
||||
branch.setIntPref(name, value);
|
||||
break;
|
||||
case "boolean":
|
||||
branch.setBoolPref(name, value);
|
||||
break;
|
||||
default:
|
||||
vimperator.echoerr("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
|
||||
}
|
||||
}
|
||||
|
||||
function loadPreference(name, forced_default, vimperator_branch)
|
||||
{
|
||||
var default_value = null;
|
||||
if (forced_default != null) // this argument sets defaults for non-user settable options (like comp_history)
|
||||
default_value = forced_default;
|
||||
|
||||
if (vimperator_branch)
|
||||
{
|
||||
branch = vimperator_prefs;
|
||||
|
||||
if (!forced_default) // this argument sets defaults for non-user settable options (like comp_history)
|
||||
{
|
||||
for (var i = 0; i < options.length; i++)
|
||||
{
|
||||
if (options[i].name == name) // only first name is searched
|
||||
{
|
||||
default_value = options[i].default_value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
branch = firefox_prefs;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (typeof default_value)
|
||||
{
|
||||
case "string":
|
||||
return branch.getCharPref(name);
|
||||
case "number":
|
||||
return branch.getIntPref(name);
|
||||
case "boolean":
|
||||
return branch.getBoolPref(name);
|
||||
default:
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
|
||||
function setGuiOptions(value)
|
||||
{
|
||||
// hide menubar
|
||||
document.getElementById("toolbar-menubar").collapsed = value.indexOf("m") > -1 ? false : true;
|
||||
document.getElementById("toolbar-menubar").hidden = value.indexOf("m") > -1 ? false : true;
|
||||
// and main toolbar
|
||||
document.getElementById("nav-bar").collapsed = value.indexOf("T") > -1 ? false : true;
|
||||
document.getElementById("nav-bar").hidden = value.indexOf("T") > -1 ? false : true;
|
||||
// and bookmarks toolbar
|
||||
document.getElementById("PersonalToolbar").collapsed = value.indexOf("b") > -1 ? false : true;
|
||||
document.getElementById("PersonalToolbar").hidden = value.indexOf("b") > -1 ? false : true;
|
||||
// and original status bar (default), but show it, e.g. when needed for extensions
|
||||
document.getElementById("status-bar").collapsed = value.indexOf("s") > -1 ? false : true;
|
||||
document.getElementById("status-bar").hidden = value.indexOf("s") > -1 ? false : true;
|
||||
}
|
||||
|
||||
function setShowTabline(value)
|
||||
{
|
||||
// hide tabbar
|
||||
if (value == 0)
|
||||
{
|
||||
getBrowser().mStrip.collapsed = true;
|
||||
getBrowser().mStrip.hidden = true;
|
||||
}
|
||||
else if (value == 1)
|
||||
vimperator.echo("show tabline only with > 1 page open not impl. yet");
|
||||
else
|
||||
{
|
||||
getBrowser().mStrip.collapsed = false;
|
||||
getBrowser().mStrip.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
function setTitleString(value)
|
||||
{
|
||||
document.getElementById("main-window").setAttribute("titlemodifier", value);
|
||||
if (window.content.document.title.length > 0)
|
||||
document.title = window.content.document.title + " - " + value;
|
||||
else
|
||||
document.title = value;
|
||||
}
|
||||
|
||||
function setPopups(value)
|
||||
{
|
||||
var values = [ [0, 1], // always in current tab
|
||||
[0, 3], // in a new tab
|
||||
[2, 3], // in a new window if it has specified sizes
|
||||
[1, 2]];// always in new window
|
||||
storePreference("browser.link.open_newwindow.restriction", values[value][0]);
|
||||
storePreference("browser.link.open_newwindow", values[value][1]);
|
||||
}
|
||||
|
||||
//
|
||||
// firefox preferences which need to be changed to work well with vimperator
|
||||
//
|
||||
|
||||
// work around firefox popup blocker
|
||||
var popup_allowed_events = loadPreference('dom.popup_allowed_events', 'change click dblclick mouseup reset submit');
|
||||
if (!popup_allowed_events.match("keypress"))
|
||||
storePreference('dom.popup_allowed_events', popup_allowed_events + " keypress");
|
||||
|
||||
// TODO: shouldn't we be resetting these in destroy() as well?
|
||||
// we have our own typeahead find implementation
|
||||
storePreference('accessibility.typeaheadfind.autostart', false);
|
||||
storePreference('accessibility.typeaheadfind', false); // actually the above setting should do it, but has no effect in firefox
|
||||
|
||||
// start with saved session
|
||||
storePreference("browser.startup.page", 3);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
this.__iterator__ = function()
|
||||
{
|
||||
return optionsIterator();
|
||||
}
|
||||
|
||||
this.get = function(name)
|
||||
{
|
||||
for (var i = 0; i < options.length; i++)
|
||||
{
|
||||
if (options[i].hasName(name))
|
||||
return options[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
this.destroy = function()
|
||||
{
|
||||
// reset some modified firefox prefs
|
||||
if (loadPreference('dom.popup_allowed_events', 'change click dblclick mouseup reset submit')
|
||||
== popup_allowed_events + " keypress")
|
||||
storePreference('dom.popup_allowed_events', popup_allowed_events);
|
||||
}
|
||||
|
||||
this.list = function()
|
||||
{
|
||||
// TODO: columns like Vim?
|
||||
var list = "<table style=\"white-space: nowrap;\">" +
|
||||
"<tr align=\"left\" style=\"color: magenta\"><th>--- Options ---</th></tr>";
|
||||
var name, value;
|
||||
|
||||
for (var i = 0; i < options.length; i++)
|
||||
{
|
||||
name = options[i].name;
|
||||
value = options[i].value;
|
||||
|
||||
if (options[i].type == "boolean")
|
||||
{
|
||||
name = value ? " " + name : "no" + name;
|
||||
list += "<tr><td>" + name + "</td></tr>";
|
||||
}
|
||||
else
|
||||
{
|
||||
list += "<tr><td>" + " " + name + "=" + value + "</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
list += "</table>";
|
||||
|
||||
vimperator.commandline.echo(list, true);
|
||||
}
|
||||
|
||||
// TODO: separate Preferences from Options? Would these utility functions
|
||||
// be better placed in the 'core' vimperator namespace somewhere?
|
||||
Options.setPref = function(name, value)
|
||||
{
|
||||
return storePreference(name, value, true);
|
||||
}
|
||||
|
||||
Options.getPref = function(name, forced_default)
|
||||
{
|
||||
return loadPreference(name, forced_default, true);
|
||||
}
|
||||
|
||||
Options.setFirefoxPref = function(name, value)
|
||||
{
|
||||
return storePreference(name, value);
|
||||
}
|
||||
|
||||
Options.getFirefoxPref = function(name, forced_default)
|
||||
{
|
||||
return loadPreference(name, forced_default);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// DEFAULT OPTIONS /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
const DEFAULT_HINTTAGS = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " +
|
||||
"//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
|
||||
"//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " +
|
||||
"//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | //xhtml:button | //xhtml:select"
|
||||
|
||||
addOption(new Option(["activate", "act"], "stringlist",
|
||||
{
|
||||
short_help: "Define when tabs are automatically activated",
|
||||
help: "Available items:<br/>" +
|
||||
"<ul>" +
|
||||
"<li><b>homepage</b>: <code class=\"mapping\">gH</code> mapping</li>" +
|
||||
"<li><b>quickmark</b>: <code class=\"mapping\">go</code> and <code class=\"mapping\">gn</code> mappings</li>" +
|
||||
"<li><b>tabopen</b>: <code class=\"command\">:tabopen[!]</code> command</li>" +
|
||||
"<li><b>paste</b>: <code class=\"mapping\">P</code> and <code class=\"mapping\">gP</code> mappings</li>" +
|
||||
"</ul>",
|
||||
default_value: "homepage,quickmark,tabopen,paste"
|
||||
}
|
||||
));
|
||||
addOption(new Option(["autohints", "ah"], "boolean",
|
||||
{
|
||||
short_help: "Automatically show hints on every web page",
|
||||
default_value: false
|
||||
}
|
||||
));
|
||||
addOption(new Option(["complete", "cpt"], "charlist",
|
||||
{
|
||||
short_help: "Items which are completed at the :[tab]open prompt",
|
||||
help: "Available items:<br/><ul>" +
|
||||
"<li><b>s</b>: Search engines and keyword URLs</li>" +
|
||||
"<li><b>f</b>: Local files</li>" +
|
||||
"<li><b>b</b>: Bookmarks</li>" +
|
||||
"<li><b>h</b>: History</li></ul>" +
|
||||
"The order is important, so <code class=\"command\">:set complete=bs</code> would list bookmarks first, and then any available quick searches.<br/>" +
|
||||
"Add <code class=\"option\">'sort'</code> to the <code class=\"option\">'wildoptions'</code> option if you want all entries sorted.",
|
||||
default_value: "sfbh",
|
||||
validator: function (value) { if (/[^sfbh]/.test(value)) return false; else return true; }
|
||||
}
|
||||
));
|
||||
addOption(new Option(["defsearch", "ds"], "string",
|
||||
{
|
||||
short_help: "Set the default search engine",
|
||||
help: "The default search engine is used in the <code class=\"command\">:[tab]open [arg]</code> command " +
|
||||
"if [arg] neither looks like a URL or like a specified search engine/keyword.",
|
||||
completer: function() { return [["foo", "bar"], ["shit", "blub"]]; },
|
||||
default_value: "google"
|
||||
}
|
||||
));
|
||||
addOption(new Option(["extendedhinttags", "eht"], "string",
|
||||
{
|
||||
short_help: "XPath string of hintable elements activated by ';'",
|
||||
default_value: DEFAULT_HINTTAGS
|
||||
}
|
||||
));
|
||||
addOption(new Option(["focusedhintstyle", "fhs"], "string",
|
||||
{
|
||||
short_help: "CSS specification of focused hints",
|
||||
default_value: "z-index:5000; font-family:monospace; font-size:12px; color:ButtonText; background-color:ButtonShadow; " +
|
||||
"border-color:ButtonShadow; border-width:1px; border-style:solid; padding:0px 1px 0px 1px; position:absolute;"
|
||||
}
|
||||
));
|
||||
addOption(new Option(["fullscreen", "fs"], "boolean",
|
||||
{
|
||||
short_help: "Show the current window fullscreen",
|
||||
setter: function(value) { window.fullScreen = value; },
|
||||
getter: function() { return window.fullScreen; },
|
||||
default_value: false
|
||||
}
|
||||
));
|
||||
addOption(new Option(["guioptions", "go"], "charlist",
|
||||
{
|
||||
short_help: "Show or hide the menu, toolbar and scrollbars",
|
||||
help: "Supported characters:<br/><ul>" +
|
||||
"<li><b>m</b>: menubar</li>" +
|
||||
"<li><b>T</b>: toolbar</li>" +
|
||||
"<li><b>b</b>: bookmark bar</li>" +
|
||||
"<li><b>s</b>: statusbar</li></ul>",
|
||||
setter: function(value) { Options.setPref("guioptions", value); setGuiOptions(value); },
|
||||
default_value: "s",
|
||||
validator: function (value) { if (/[^mTbs]/.test(value)) return false; else return true; }
|
||||
}
|
||||
));
|
||||
addOption(new Option(["hintchars", "hc"], "charlist",
|
||||
{
|
||||
short_help: "String of single characters which can be used to follow hints",
|
||||
default_value: "hjklasdfgyuiopqwertnmzxcvb"
|
||||
}
|
||||
));
|
||||
addOption(new Option(["hintstyle", "hs"], "string",
|
||||
{
|
||||
short_help: "CSS specification of unfocused hints",
|
||||
default_value: "z-index:5000; font-family:monospace; font-size:12px; color:white; background-color:red; " +
|
||||
"border-color:ButtonShadow; border-width:0px; border-style:solid; padding:0px 1px 0px 1px; position:absolute;"
|
||||
}
|
||||
));
|
||||
addOption(new Option(["hinttags", "ht"], "string",
|
||||
{
|
||||
short_help: "XPath string of hintable elements activated by <code class=\"mapping\">'f'</code> and <code class=\"mapping\">'F'</code>",
|
||||
default_value: DEFAULT_HINTTAGS
|
||||
}
|
||||
));
|
||||
addOption(new Option(["hlsearch", "hls"], "boolean",
|
||||
{
|
||||
short_help: "Highlight previous search pattern matches",
|
||||
setter: function(value) { Options.setPref("hlsearch", value); if (value) vimperator.search.highlight(); else vimperator.search.clear(); },
|
||||
default_value: false
|
||||
}
|
||||
));
|
||||
addOption(new Option(["hlsearchstyle", "hlss"], "string",
|
||||
{
|
||||
short_help: "CSS specification of highlighted search items",
|
||||
default_value: "color: black; background-color: yellow; padding: 0; display: inline;"
|
||||
}
|
||||
));
|
||||
addOption(new Option(["ignorecase", "ic"], "boolean",
|
||||
{
|
||||
short_help: "Ignore case in search patterns",
|
||||
default_value: true
|
||||
}
|
||||
));
|
||||
addOption(new Option(["incsearch", "is"], "boolean",
|
||||
{
|
||||
short_help: "Show where the search pattern matches as it is typed",
|
||||
help: "NOTE: Incremental searching currently only works in the forward direction.",
|
||||
default_value: true
|
||||
}
|
||||
));
|
||||
addOption(new Option(["insertmode", "im"], "boolean",
|
||||
{
|
||||
short_help: "Use Insert mode as the default for text areas",
|
||||
help: "Makes Vimperator work in a way that Insert mode is the default mode for text areas. " +
|
||||
"Useful if you want to use Vimperator as a modeless editor, keeping the known Firefox interface for editing text areas.",
|
||||
default_value: true
|
||||
}
|
||||
));
|
||||
addOption(new Option(["linksearch", "ls"], "boolean",
|
||||
{
|
||||
short_help: "Limit the search to hyperlink text",
|
||||
help: "This includes (X)HTML elements with an \"href\" atrribute and XLink \"simple\" links.",
|
||||
default_value: false
|
||||
}
|
||||
));
|
||||
addOption(new Option(["more"], "boolean",
|
||||
{
|
||||
short_help: "Pause the message list window when more than one screen of listings is displayed",
|
||||
default_value: true
|
||||
}
|
||||
));
|
||||
addOption(new Option(["maxhints", "mh"], "number",
|
||||
{
|
||||
short_help: "Maximum number of simultaneously shown hints",
|
||||
help: "If you want to speed up display of hints, choose a smaller value",
|
||||
default_value: 250,
|
||||
validator: function (value) { if (value >= 1 && value <= 1000) return true; else return false; }
|
||||
}
|
||||
));
|
||||
addOption(new Option(["popups", "pps"], "number",
|
||||
{
|
||||
short_help: "Where to show requested popup windows",
|
||||
help: "Define where to show requested popup windows. Does not apply to windows which are opened by middle clicking a link, they always open in a new tab. " +
|
||||
"Possible values:<br/><ul>" +
|
||||
"<li><b>0</b>: Force to open in the current tab (NOTE: this can stop some web sites from working correctly!)</li>" +
|
||||
"<li><b>1</b>: Always open in a new tab</li>" +
|
||||
"<li><b>2</b>: Open in a new window if it has a specific requested size (default in Firefox)</li>"+
|
||||
"<li><b>3</b>: Always open in a new window</li></ul>" +
|
||||
"NOTE: This option does not change the popup blocker of Firefox in any way.",
|
||||
default_value: 1,
|
||||
setter: function(value) { Options.setPref("popups", value); setPopups(value); },
|
||||
validator: function (value) { if (value >= 0 && value <= 3) return true; else return false; }
|
||||
}
|
||||
));
|
||||
addOption(new Option(["preload"], "boolean",
|
||||
{
|
||||
short_help: "Speed up first time history/bookmark completion",
|
||||
help: "History access can be quite slow for a large history. Vimperator maintains a cache to speed it up significantly on subsequent access.<br/>" +
|
||||
"In order to also speed up first time access, it is cached at startup, if this option is set (recommended).",
|
||||
default_value: true
|
||||
}
|
||||
));
|
||||
addOption(new Option(["previewheight", "pvh"], "number",
|
||||
{
|
||||
short_help: "Default height for preview window",
|
||||
help: "Value must be between 1 and 50. If the value is too high, completions may cover the command-line. " +
|
||||
"Close the preview window with <code class=\"command\">:pclose</code>.<br/>" +
|
||||
"NOTE: Option currently disabled",
|
||||
default_value: 10,
|
||||
validator: function (value) { if (value >= 1 && value <= 50) return true; else return false; }
|
||||
}
|
||||
));
|
||||
addOption(new Option(["scroll", "scr"], "number",
|
||||
{
|
||||
short_help: "Number of lines to scroll with <code class=\"mapping\">C-u</code> and <code class=\"mapping\">C-d</code> commands",
|
||||
help: "The number of lines scrolled defaults to half the window size. " +
|
||||
"When a <code class=\"argument\">{count}</code> is specified to the <code class=\"mapping\"><C-u></code> or <code class=\"mapping\"><C-d></code> commands this is used to set the value of <code class=\"option\">'scroll'</code> and also used for the current command. " +
|
||||
"The value can be reset to half the window height with <code class=\"command\">:set scroll=0</code>.",
|
||||
default_value: 0,
|
||||
validator: function (value) { if (value >= 0) return true; else return false; }
|
||||
}
|
||||
));
|
||||
addOption(new Option(["showmode", "smd"], "boolean",
|
||||
{
|
||||
short_help: "Show the current mode in the command line",
|
||||
default_value: true
|
||||
}
|
||||
));
|
||||
addOption(new Option(["showstatuslinks", "ssli"], "number",
|
||||
{
|
||||
short_help: "Show the destination of the link under the cursor in the status bar",
|
||||
help: "Also links which are focused by keyboard commands like <code class=\"mapping\"><Tab></code> are shown. " +
|
||||
"Possible values:<br/><ul>" +
|
||||
"<li><b>0</b>: Don't show link destination</li>" +
|
||||
"<li><b>1</b>: Show the link in the status line</li>" +
|
||||
"<li><b>2</b>: Show the link in the command line</li></ul>",
|
||||
default_value: 1,
|
||||
validator: function (value) { if (value >= 0 && value <= 2) return true; else return false; }
|
||||
}
|
||||
));
|
||||
addOption(new Option(["showtabline", "stal"], "number",
|
||||
{
|
||||
short_help: "Control when to show the tab bar of opened web pages",
|
||||
help: "Possible values:<br/><ul>" +
|
||||
"<li><b>0</b>: Never show tab bar</li>" +
|
||||
"<li><b>1</b>: Show tab bar only if more than one tab is open</li>" +
|
||||
"<li><b>2</b>: Always show tab bar</li></ul>" +
|
||||
"NOTE: Not fully implemented yet and buggy with stal=0",
|
||||
setter: function(value) { Options.setPref("showtabline", value); setShowTabline(value); },
|
||||
default_value: 2,
|
||||
validator: function (value) { if (value >= 0 && value <= 2) return true; else return false; }
|
||||
}
|
||||
));
|
||||
addOption(new Option(["smartcase", "scs"], "boolean",
|
||||
{
|
||||
short_help: "Override the 'ignorecase' option if the pattern contains uppercase characters",
|
||||
help: "This is only used if the <code class=\"option\">'ignorecase'</code> option is set.",
|
||||
default_value: true
|
||||
}
|
||||
));
|
||||
addOption(new Option(["titlestring"], "string",
|
||||
{
|
||||
short_help: "Change the title of the browser window",
|
||||
help: "Vimperator changes the browser title from \"Title of web page - Mozilla Firefox\" to " +
|
||||
"\"Title of web page - Vimperator\".<br/>If you don't like that, you can restore it with: " +
|
||||
"<code class=\"command\">:set titlestring=Mozilla Firefox</code>.",
|
||||
setter: function(value) { Options.setPref("titlestring", value); setTitleString(value); },
|
||||
default_value: "Vimperator"
|
||||
}
|
||||
));
|
||||
addOption(new Option(["usermode", "um"], "boolean",
|
||||
{
|
||||
short_help: "Show current website with a minimal style sheet to make it easily accessible",
|
||||
help: "Note that this is a local option for now, later it may be split into a global and <code class=\"command\">:setlocal</code> part",
|
||||
setter: function(value) { getMarkupDocumentViewer().authorStyleDisabled = value; },
|
||||
getter: function() { return getMarkupDocumentViewer().authorStyleDisabled; },
|
||||
default_value: false
|
||||
}
|
||||
));
|
||||
addOption(new Option(["verbose", "vbs"], "number",
|
||||
{
|
||||
short_help: "Define which type of messages are logged",
|
||||
help: "When bigger than zero, Vimperator will give messages about what it is doing. They are printed to the error console which can be shown with <code class=\"command\">:javascript!</code>.<br/>" +
|
||||
"The highest value is 9, being the most verbose mode.",
|
||||
default_value: 0,
|
||||
validator: function (value) { if (value >= 0 && value <= 9) return true; else return false; }
|
||||
}
|
||||
));
|
||||
addOption(new Option(["visualbell", "vb"], "boolean",
|
||||
{
|
||||
short_help: "Use visual bell instead of beeping on errors",
|
||||
setter: function(value) { Options.setPref("visualbell", value); Options.setFirefoxPref("accessibility.typeaheadfind.enablesound", !value); },
|
||||
default_value: false
|
||||
}
|
||||
));
|
||||
addOption(new Option(["wildmode", "wim"], "stringlist",
|
||||
{
|
||||
short_help: "Define how command line completion works",
|
||||
help: "It is a comma-separated list of parts, where each part specifies " +
|
||||
"what to do for each consecutive use of the completion key. The first part " +
|
||||
"specifies the behavior for the first use of the completion key, the second part " +
|
||||
"for the second use, etc.<br/>" +
|
||||
"These are the possible values for each part:<br/>" +
|
||||
"<table>" +
|
||||
"<tr><td><b>''</b></td><td>Complete only the first match</td></tr>" +
|
||||
"<tr><td><b>'full'</b></td><td>Complete the next full match. After the last, the original string is used.</td></tr>" +
|
||||
"<tr><td><b>'longest'</b></td><td>Complete till the longest common string.</td></tr>" +
|
||||
"<tr><td><b>'list'</b></td><td>When more than one match, list all matches.</td></tr>" +
|
||||
"<tr><td><b>'list:full'</b></td><td>When more than one match, list all matches and complete first match.</td></tr>" +
|
||||
"<tr><td><b>'list:longest'</b></td><td>When more than one match, list all matches and complete till the longest common string.</td></tr>" +
|
||||
"</table>" +
|
||||
"When there is only a single match, it is fully completed regardless of the case.",
|
||||
default_value: "list:full",
|
||||
validator: function (value)
|
||||
{
|
||||
if (/^(?:(?:full|longest|list|list:full|list:longest)(?:,(?!,))?){0,3}(?:full|longest|list|list:full|list:longest)?$/.test(value))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
));
|
||||
addOption(new Option(["wildoptions", "wop"], "stringlist",
|
||||
{
|
||||
short_help: "Change how command line completion is done",
|
||||
help: "A list of words that change how command line completion is done.<br/>" +
|
||||
"Currently only one word is allowed:<br/>" +
|
||||
"<table>" +
|
||||
"<tr><td><b>sort</b></td><td>Always sorts completion list, overriding the <code class=\"option\">'complete'</code> option.</td></tr>" +
|
||||
"</table>",
|
||||
default_value: "",
|
||||
validator: function (value) { if (/^sort$/.test(value)) return true; else return false; }
|
||||
}
|
||||
));
|
||||
//}}}
|
||||
|
||||
setShowTabline(this.showtabline);
|
||||
setGuiOptions(this.guioptions);
|
||||
setTitleString(this.titlestring);
|
||||
setPopups(this.popups);
|
||||
} //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
232
content/tabs.js
Normal file
232
content/tabs.js
Normal file
@@ -0,0 +1,232 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
/**
|
||||
* provides functions for working with tabs
|
||||
* XXX: ATTENTION: We are planning to move to the FUEL API once we switch to
|
||||
* Firefox 3.0, then this class should go away and their tab methods should be used
|
||||
* @deprecated
|
||||
*/
|
||||
function Tabs() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
/** @param spec can either be:
|
||||
* - an absolute integer
|
||||
* - "" for the current tab
|
||||
* - "+1" for the next tab
|
||||
* - "-3" for the tab, which is 3 positions left of the current
|
||||
* - "$" for the last tab
|
||||
*/
|
||||
function indexFromSpec(spec, wrap)
|
||||
{
|
||||
var position = getBrowser().tabContainer.selectedIndex;
|
||||
var length = getBrowser().mTabs.length;
|
||||
var last = length - 1;
|
||||
|
||||
if (spec === undefined || spec === "")
|
||||
return position;
|
||||
|
||||
if (typeof spec === "number")
|
||||
position = spec;
|
||||
else if (spec === "$")
|
||||
return last;
|
||||
else if (!spec.match(/^([+-]?\d+|)$/))
|
||||
{
|
||||
// TODO: move error reporting to ex-command?
|
||||
vimperator.echoerr("E488: Trailing characters");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (spec.match(/^([+-]\d+)$/)) // relative position +/-N
|
||||
position += parseInt(spec);
|
||||
else // absolute position
|
||||
position = parseInt(spec);
|
||||
}
|
||||
|
||||
if (position > last)
|
||||
position = wrap ? position % length : last;
|
||||
else if (position < 0)
|
||||
position = wrap ? (position % length) + length: 0;
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
var alternates = [null, null];
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// @returns the index of the currently selected tab starting with 0
|
||||
this.index = function(tab)
|
||||
{
|
||||
if (tab)
|
||||
{
|
||||
var length = getBrowser().mTabs.length;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
if (getBrowser().mTabs[i] == tab)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return getBrowser().tabContainer.selectedIndex;
|
||||
}
|
||||
|
||||
this.count = function()
|
||||
{
|
||||
return getBrowser().mTabs.length;
|
||||
}
|
||||
|
||||
// TODO: implement filter
|
||||
// @returns an array of tabs which match filter
|
||||
this.get = function(filter)
|
||||
{
|
||||
var buffers = [];
|
||||
var browsers = getBrowser().browsers;
|
||||
for (var i in browsers)
|
||||
{
|
||||
var title = browsers[i].contentTitle || "(Untitled)";
|
||||
var uri = browsers[i].currentURI.spec;
|
||||
var number = i + 1;
|
||||
buffers.push([number, title, uri]);
|
||||
}
|
||||
return buffers;
|
||||
}
|
||||
|
||||
this.getTab = function(index)
|
||||
{
|
||||
if (index)
|
||||
return getBrowser().mTabs[index];
|
||||
|
||||
return getBrowser().tabContainer.selectedItem;
|
||||
}
|
||||
|
||||
/* spec == "" moves the tab to the last position as per Vim
|
||||
* wrap causes the movement to wrap around the start and end of the tab list
|
||||
* NOTE: position is a 0 based index
|
||||
* FIXME: tabmove! N should probably produce an error
|
||||
*/
|
||||
this.move = function(tab, spec, wrap)
|
||||
{
|
||||
if (spec === "")
|
||||
spec = "$"; // if not specified, move to the last tab -> XXX: move to ex handling?
|
||||
|
||||
var index = indexFromSpec(spec, wrap);
|
||||
getBrowser().moveTabTo(tab, index);
|
||||
}
|
||||
|
||||
/* quit_on_last_tab = 1: quit without saving session
|
||||
* quit_on_last_tab = 2: quit and save session
|
||||
*/
|
||||
this.remove = function(tab, count, focus_left_tab, quit_on_last_tab)
|
||||
{
|
||||
if (count < 1) count = 1;
|
||||
|
||||
if (quit_on_last_tab >= 1 && getBrowser().mTabs.length <= count)
|
||||
vimperator.quit(quit_on_last_tab == 2);
|
||||
|
||||
if (focus_left_tab && tab.previousSibling)
|
||||
this.select("-1", false);
|
||||
|
||||
getBrowser().removeTab(tab);
|
||||
}
|
||||
|
||||
this.keepOnly = function(tab)
|
||||
{
|
||||
getBrowser().removeAllTabsBut(tab);
|
||||
}
|
||||
|
||||
this.select = function(spec, wrap)
|
||||
{
|
||||
var index = indexFromSpec(spec, wrap);
|
||||
if (index === false)
|
||||
{
|
||||
vimperator.beep(); // XXX: move to ex-handling?
|
||||
return false;
|
||||
}
|
||||
getBrowser().mTabContainer.selectedIndex = index;
|
||||
}
|
||||
|
||||
// TODO: when restarting a session FF selects the first tab and then the
|
||||
// tab that was selected when the session was created. As a result the
|
||||
// alternate after a restart is often incorrectly tab 1 when there
|
||||
// shouldn't be one yet.
|
||||
this.updateSelectionHistory = function()
|
||||
{
|
||||
alternates = [this.getTab(), alternates[0]];
|
||||
this.alternate = alternates[1];
|
||||
}
|
||||
|
||||
// TODO: move to v.buffers
|
||||
this.alternate = this.getTab();
|
||||
|
||||
this.reload = function(tab, bypass_cache)
|
||||
{
|
||||
if (bypass_cache)
|
||||
{
|
||||
const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
|
||||
const flags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
|
||||
getBrowser().getBrowserForTab(tab).reloadWithFlags(flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
getBrowser().reloadTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
this.reloadAll = function(bypass_cache)
|
||||
{
|
||||
if (bypass_cache)
|
||||
{
|
||||
for (var i = 0; i < getBrowser().mTabs.length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.reload(getBrowser().mTabs[i], bypass_cache)
|
||||
}
|
||||
catch (e) {
|
||||
// FIXME: can we do anything useful here without stopping the
|
||||
// other tabs from reloading?
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getBrowser().reloadAllTabs();
|
||||
}
|
||||
}
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
1128
content/ui.js
Normal file
1128
content/ui.js
Normal file
File diff suppressed because it is too large
Load Diff
833
content/vimperator.js
Normal file
833
content/vimperator.js
Normal file
@@ -0,0 +1,833 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
const vimperator = (function() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// our services
|
||||
var sound_service = Components.classes['@mozilla.org/sound;1']
|
||||
.getService(Components.interfaces.nsISound);
|
||||
var console_service = Components.classes['@mozilla.org/consoleservice;1']
|
||||
.getService(Components.interfaces.nsIConsoleService);
|
||||
var environment_service = Components.classes["@mozilla.org/process/environment;1"]
|
||||
.getService(Components.interfaces.nsIEnvironment);
|
||||
|
||||
var callbacks = [];
|
||||
|
||||
|
||||
function expandPath(path)
|
||||
{
|
||||
const WINDOWS = navigator.platform == "Win32";
|
||||
|
||||
// TODO: proper pathname separator translation like Vim
|
||||
if (WINDOWS)
|
||||
path = path.replace('/', '\\', 'g');
|
||||
|
||||
// expand "~" to VIMPERATOR_HOME or HOME (USERPROFILE or HOMEDRIVE\HOMEPATH on Windows if HOME is not set)
|
||||
if (/^~/.test(path))
|
||||
{
|
||||
var home = environment_service.get("VIMPERATOR_HOME");
|
||||
|
||||
if (!home)
|
||||
home = environment_service.get("HOME");
|
||||
|
||||
if (WINDOWS && !home)
|
||||
home = environment_service.get("USERPROFILE") ||
|
||||
environment_service.get("HOMEDRIVE") + environment_service.get("HOMEPATH");
|
||||
|
||||
path = path.replace("~", home);
|
||||
}
|
||||
|
||||
// expand any $ENV vars
|
||||
var env_vars = path.match(/\$\w+\b/g); // this is naive but so is Vim and we like to be compatible
|
||||
|
||||
if (env_vars)
|
||||
{
|
||||
var expansion;
|
||||
|
||||
for (var i = 0; i < env_vars.length; i++)
|
||||
{
|
||||
expansion = environment_service.get(env_vars[i].replace("$", ""));
|
||||
if (expansion)
|
||||
path = path.replace(env_vars[i], expansion);
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// TODO: add this functionality to LocalFile or wait for Scriptable I/O in FUEL
|
||||
function pathExists(path)
|
||||
{
|
||||
var p = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
p.initWithPath(expandPath(path));
|
||||
|
||||
return p.exists();
|
||||
}
|
||||
|
||||
function getPluginDir()
|
||||
{
|
||||
var plugin_dir;
|
||||
|
||||
if (navigator.platform == "Win32")
|
||||
plugin_dir = "~/vimperator/plugin";
|
||||
else
|
||||
plugin_dir = "~/.vimperator/plugin";
|
||||
|
||||
plugin_dir = expandPath(plugin_dir);
|
||||
|
||||
return pathExists(plugin_dir) ? plugin_dir : null;
|
||||
}
|
||||
|
||||
function getRCFile()
|
||||
{
|
||||
var rc_file1 = expandPath("~/.vimperatorrc");
|
||||
var rc_file2 = expandPath("~/_vimperatorrc");
|
||||
|
||||
if (navigator.platform == "Win32")
|
||||
[rc_file1, rc_file2] = [rc_file2, rc_file1]
|
||||
|
||||
if (pathExists(rc_file1))
|
||||
return rc_file1;
|
||||
else if (pathExists(rc_file2))
|
||||
return rc_file2;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: make secure
|
||||
// TODO: test if it actually works on windows
|
||||
function getTempFile()
|
||||
{
|
||||
var file = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
if (navigator.platform == "Win32")
|
||||
{
|
||||
var dir = environment_service.get("TMP") || environment_service.get("TEMP") || "C:\\";
|
||||
file.initWithPath(dir + "\\vimperator.tmp");
|
||||
}
|
||||
else
|
||||
{
|
||||
var dir = environment_service.get("TMP") || environment_service.get("TEMP") || "/tmp/";
|
||||
file.initWithPath(dir + "/vimperator.tmp");
|
||||
}
|
||||
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
|
||||
|
||||
if (file.exists())
|
||||
return file;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
return {
|
||||
get mode() { return vimperator.modes.main; },
|
||||
set mode(value) { vimperator.modes.main = value; },
|
||||
|
||||
// Global contants
|
||||
CURRENT_TAB: 1,
|
||||
NEW_TAB: 2,
|
||||
NEW_BACKGROUND_TAB: 3,
|
||||
NEW_WINDOW: 4,
|
||||
|
||||
// ###VERSION### and ###DATE### are replaced by the Makefile
|
||||
version: "###VERSION### (created: ###DATE###)",
|
||||
|
||||
input: {
|
||||
buffer: "", // partial command storage
|
||||
pendingMotionMap: null, // e.g. "d{motion}" if we wait for a motion of the "d" command
|
||||
pendingArgMap: null, // pending map storage for commands like m{a-z}
|
||||
count: -1 // parsed count from the input buffer
|
||||
},
|
||||
|
||||
// @param type can be:
|
||||
// "submit": when the user pressed enter in the command line
|
||||
// "change"
|
||||
// "cancel"
|
||||
// "complete"
|
||||
// TODO: "zoom": if the zoom value of the current buffer changed
|
||||
registerCallback: function(type, mode, func)
|
||||
{
|
||||
// TODO: check if callback is already registered
|
||||
callbacks.push([type, mode, func]);
|
||||
},
|
||||
|
||||
triggerCallback: function(type, mode, data)
|
||||
{
|
||||
// dump("type: " + type + " mode: " + mode + "data: " + data + "\n");
|
||||
for (var i in callbacks)
|
||||
{
|
||||
var [thistype, thismode, thisfunc] = callbacks[i];
|
||||
if (mode == thismode && type == thistype)
|
||||
return thisfunc.call(this, data);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
beep: function()
|
||||
{
|
||||
if (!vimperator.options["visualbell"])
|
||||
{
|
||||
sound_service.beep();
|
||||
return;
|
||||
}
|
||||
|
||||
// flash the visual bell
|
||||
var popup = document.getElementById("vimperator-visualbell");
|
||||
var win = getBrowser().mPanelContainer;
|
||||
var box = document.getBoxObjectFor(win);
|
||||
|
||||
popup.height = box.height;
|
||||
popup.width = box.width;
|
||||
//popup.style.backgroundColor = "black";
|
||||
////popup.showPopup(win, box.screenX, box.screenY, "popup");
|
||||
//popup.showPopup(win, -1, -1, "popup", "topleft", "topleft");
|
||||
|
||||
popup.openPopup(win, "overlap", 0, 0, false, false)
|
||||
|
||||
setTimeout(function() { popup.hidePopup(); }, 50);
|
||||
},
|
||||
|
||||
copyToClipboard: function(str)
|
||||
{
|
||||
var clipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
|
||||
.getService(Components.interfaces.nsIClipboardHelper);
|
||||
clipboardHelper.copyString(str);
|
||||
},
|
||||
|
||||
execute: function(string, modifiers)
|
||||
{
|
||||
// skip comments and blank lines
|
||||
if (/^\s*("|$)/.test(string))
|
||||
return;
|
||||
|
||||
if (!modifiers)
|
||||
modifiers = {};
|
||||
|
||||
var [count, cmd, special, args] = vimperator.commands.parseCommand(string.replace(/^'(.*)'$/, '$1'));
|
||||
var command = vimperator.commands.get(cmd);
|
||||
|
||||
if (command === null)
|
||||
{
|
||||
vimperator.echoerr("E492: Not an editor command: " + cmd);
|
||||
vimperator.focusContent();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: need to perform this test? -- djk
|
||||
if (command.action === null)
|
||||
{
|
||||
vimperator.echoerr("E666: Internal error: command.action === null");
|
||||
return;
|
||||
}
|
||||
|
||||
// valid command, call it:
|
||||
command.execute(args, special, count, modifiers);
|
||||
},
|
||||
|
||||
// after pressing Escape, put focus on a non-input field of the browser document
|
||||
focusContent: function()
|
||||
{
|
||||
var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
|
||||
getService(Components.interfaces.nsIWindowWatcher);
|
||||
|
||||
if (window == ww.activeWindow && document.commandDispatcher.focusedElement)
|
||||
document.commandDispatcher.focusedElement.blur();
|
||||
|
||||
content.focus(); // FIXME: shouldn't be window.document.content?
|
||||
},
|
||||
|
||||
fopen: function(path, mode, perms, tmp)
|
||||
{
|
||||
return new LocalFile(path, mode, perms, tmp);
|
||||
},
|
||||
|
||||
// partial sixth level expression evaluation
|
||||
eval: function(string)
|
||||
{
|
||||
string = string.toString().replace(/^\s*/, "").replace(/\s*$/, "");
|
||||
var match = string.match(/^&(\w+)/);
|
||||
if (match)
|
||||
{
|
||||
var opt = this.options.get(match[1]);
|
||||
if (!opt)
|
||||
{
|
||||
this.echoerr("E113: Unknown option: " + match[1]);
|
||||
return;
|
||||
}
|
||||
var type = opt.type;
|
||||
var value = opt.getter();
|
||||
if (type != "boolean" && type != "number")
|
||||
value = value.toString();
|
||||
return value;
|
||||
}
|
||||
|
||||
// String
|
||||
else if (match = string.match(/^(['"])([^\1]*?[^\\]?)\1/))
|
||||
{
|
||||
if (match)
|
||||
return match[2].toString();
|
||||
else
|
||||
{
|
||||
this.echoerr("E115: Missing quote: " + string);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Number
|
||||
else if (match = string.match(/^(\d+)$/))
|
||||
{
|
||||
return parseInt(match[1]);
|
||||
}
|
||||
|
||||
var reference = this.variableReference(string);
|
||||
if (!reference[0])
|
||||
this.echoerr("E121: Undefined variable: " + string);
|
||||
else
|
||||
return reference[0][reference[1]];
|
||||
|
||||
return;
|
||||
},
|
||||
|
||||
variableReference: function(string)
|
||||
{
|
||||
if (!string)
|
||||
return [null, null, null];
|
||||
|
||||
if (match = string.match(/^([bwtglsv]):(\w+)/)) // Variable
|
||||
{
|
||||
// Other variables should be implemented
|
||||
if (match[1] == "g")
|
||||
{
|
||||
if (match[2] in this.globalVariables)
|
||||
return [this.globalVariables, match[2], match[1]];
|
||||
else
|
||||
return [null, match[2], match[1]];
|
||||
}
|
||||
}
|
||||
else // Global variable
|
||||
{
|
||||
if (string in this.globalVariables)
|
||||
return [this.globalVariables, string, "g"];
|
||||
else
|
||||
return [null, string, "g"];
|
||||
}
|
||||
},
|
||||
|
||||
// if color = true it uses HTML markup to color certain items
|
||||
objectToString: function(object, color)
|
||||
{
|
||||
if (object === null)
|
||||
return "null";
|
||||
if (typeof object != "object")
|
||||
return false;
|
||||
|
||||
var string = "";
|
||||
var obj = "";
|
||||
try { // for window.JSON
|
||||
obj = object.toString();
|
||||
} catch (e) {
|
||||
obj = "<Object>";
|
||||
}
|
||||
|
||||
if (color)
|
||||
string += "<span style=\"color: magenta; font-weight: bold;\">" + obj + "</span>::\n";
|
||||
else
|
||||
string += obj + "::\n";
|
||||
|
||||
for (var i in object)
|
||||
{
|
||||
var value;
|
||||
try
|
||||
{
|
||||
if (i == "JSON") // without this ugly hack, ":echo window" does not work
|
||||
value = "[object JSON]";
|
||||
else
|
||||
value = object[i];
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
|
||||
if (color)
|
||||
{
|
||||
// syntax highlighting for special items
|
||||
if (typeof value === "number")
|
||||
value = "<span style=\"color: red;\">" + value + "</span>";
|
||||
else if (typeof value === "string")
|
||||
{
|
||||
value = value.replace(/\n/, "\\n").replace(/</, "<");
|
||||
value = "<span style=\"color: green;\">\"" + value + "\"</span>";
|
||||
}
|
||||
else if (typeof value === "boolean")
|
||||
value = "<span style=\"color: blue;\">" + value + "</span>";
|
||||
else if (value == null || value == "undefined")
|
||||
value = "<span style=\"color: blue;\">" + value + "</span>";
|
||||
else if (typeof value === "object" || typeof value === "function")
|
||||
{
|
||||
// for java packages value.toString() would crash so badly
|
||||
// that we cannot even try/catch it
|
||||
if (/^\[JavaPackage.*\]$/.test(value))
|
||||
value = "[JavaPackage]";
|
||||
else
|
||||
{
|
||||
var str = value.toString();
|
||||
if (typeof str == "string") // can be "undefined"
|
||||
value = str.replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
}
|
||||
|
||||
string += "<span style=\"font-weight: bold;\">" + i + "</span>: " + value + "\n";
|
||||
}
|
||||
else
|
||||
string += i + ": " + value + "\n";
|
||||
}
|
||||
return string;
|
||||
},
|
||||
|
||||
// logs a message to the javascript error console
|
||||
// if msg is an object, it is beautified
|
||||
log: function(msg, level)
|
||||
{
|
||||
//if (Options.getPref("verbose") >= level) // FIXME: hangs vimperator, probably timing issue --mst
|
||||
if (typeof msg == "object")
|
||||
msg = this.objectToString(msg, false);
|
||||
|
||||
console_service.logStringMessage('vimperator: ' + msg);
|
||||
},
|
||||
|
||||
// open one or more URLs
|
||||
//
|
||||
// @param urls: either a string or an array of urls
|
||||
// The array can look like this:
|
||||
// ["url1", "url2", "url3", ...] or:
|
||||
// [["url1", postdata1], ["url2", postdata2], ...]
|
||||
// @param where: if ommited, CURRENT_TAB is assumed
|
||||
// @param callback: not implemented, will be allowed to specify a callback function
|
||||
// which is called, when the page finished loading
|
||||
// @returns true when load was initiated, or false on error
|
||||
open: function(urls, where, callback)
|
||||
{
|
||||
// convert the string to an array of converted URLs
|
||||
// -> see String.prototype.toURLArray for more details
|
||||
if (typeof urls == "string")
|
||||
urls = urls.toURLArray();
|
||||
|
||||
if (urls.length == 0)
|
||||
return false;
|
||||
|
||||
if (!where)
|
||||
where = vimperator.CURRENT_TAB;
|
||||
|
||||
var url = typeof urls[0] == "string" ? urls[0] : urls[0][0];
|
||||
var postdata = typeof urls[0] == "string" ? null : urls[0][1];
|
||||
var whichwindow = window;
|
||||
|
||||
// decide where to load the first url
|
||||
switch (where)
|
||||
{
|
||||
case vimperator.CURRENT_TAB:
|
||||
window.loadURI(url, null, postdata); // getBrowser.loadURI() did not work with postdata in my quick experiments --mst
|
||||
break;
|
||||
|
||||
case vimperator.NEW_TAB:
|
||||
var firsttab = getBrowser().addTab(url, null, null, postdata);
|
||||
getBrowser().selectedTab = firsttab;
|
||||
break;
|
||||
|
||||
case vimperator.NEW_BACKGROUND_TAB:
|
||||
getBrowser().addTab(url, null, null, postdata);
|
||||
break;
|
||||
|
||||
case vimperator.NEW_WINDOW:
|
||||
window.open();
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
whichwindow = wm.getMostRecentWindow("navigator:browser");
|
||||
whichwindow.loadURI(url, null, postdata)
|
||||
break;
|
||||
|
||||
default:
|
||||
vimperator.echoerr("Exxx: Invalid 'where' directive in vimperator.open(...)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// all other URLs are always loaded in background
|
||||
for (var i = 1; i < urls.length; i++)
|
||||
{
|
||||
url = typeof urls[i] == "string" ? urls[i] : urls[i][0];
|
||||
postdata = typeof urls[i] == "string" ? null : urls[i][1];
|
||||
whichwindow.getBrowser().addTab(url, null, null, postdata);
|
||||
}
|
||||
|
||||
// TODO: register callbacks
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
// quit vimperator, no matter how many tabs/windows are open
|
||||
quit: function(save_session)
|
||||
{
|
||||
if (save_session)
|
||||
Options.setFirefoxPref("browser.startup.page", 3); // start with saved session
|
||||
else
|
||||
Options.setFirefoxPref("browser.startup.page", 1); // start with default homepage session
|
||||
|
||||
goQuitApplication();
|
||||
},
|
||||
|
||||
restart: function()
|
||||
{
|
||||
const nsIAppStartup = Components.interfaces.nsIAppStartup;
|
||||
|
||||
// notify all windows that an application quit has been requested.
|
||||
var os = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
|
||||
.createInstance(Components.interfaces.nsISupportsPRBool);
|
||||
os.notifyObservers(cancelQuit, "quit-application-requested", null);
|
||||
|
||||
// something aborted the quit process.
|
||||
if (cancelQuit.data)
|
||||
return;
|
||||
|
||||
// notify all windows that an application quit has been granted.
|
||||
os.notifyObservers(null, "quit-application-granted", null);
|
||||
|
||||
// enumerate all windows and call shutdown handlers
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var windows = wm.getEnumerator(null);
|
||||
while (windows.hasMoreElements())
|
||||
{
|
||||
var win = windows.getNext();
|
||||
if (("tryToClose" in win) && !win.tryToClose())
|
||||
return;
|
||||
}
|
||||
Components.classes["@mozilla.org/toolkit/app-startup;1"].getService(nsIAppStartup)
|
||||
.quit(nsIAppStartup.eRestart | nsIAppStartup.eAttemptQuit);
|
||||
},
|
||||
|
||||
run: function(program, args, blocking)
|
||||
{
|
||||
var file = Components.classes["@mozilla.org/file/local;1"].
|
||||
createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
file.initWithPath(program);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
//environment_service
|
||||
// FIXME: doesn't work on windows
|
||||
var dirs = environment_service.get("PATH").split(":");
|
||||
for (var i = 0; i < dirs.length; i++)
|
||||
{
|
||||
var path = dirs[i] + "/" + program;
|
||||
try
|
||||
{
|
||||
file.initWithPath(path);
|
||||
if (file.exists())
|
||||
break;
|
||||
} catch (e) { }
|
||||
}
|
||||
}
|
||||
if (!file.exists())
|
||||
{
|
||||
vimperator.echoerr("command not found: " + program);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var process = Components.classes["@mozilla.org/process/util;1"].
|
||||
createInstance(Components.interfaces.nsIProcess);
|
||||
process.init(file);
|
||||
|
||||
var ec = process.run(blocking, args, args.length);
|
||||
return ec;
|
||||
},
|
||||
// when https://bugzilla.mozilla.org/show_bug.cgi?id=68702 is fixed
|
||||
// is fixed, should use that instead of a tmpfile
|
||||
// TODO: make it usable on windows
|
||||
// TODO: pass "input" as stdin
|
||||
// TODO: add shell/shellcmdflag options to replace "sh" and "-c"
|
||||
system: function (str, input)
|
||||
{
|
||||
var fileout = getTempFile();
|
||||
if (!fileout)
|
||||
return "";
|
||||
|
||||
var filein = null;
|
||||
var command = str + " > \"" + fileout.path.replace('"', '\\"') + "\"";
|
||||
if (input)
|
||||
{
|
||||
filein = getTempFile();
|
||||
var fdin = vimperator.fopen(filein, ">");
|
||||
fdin.write(input);
|
||||
fdin.close();
|
||||
command += " < \"" + filein.path.replace('"', '\\"') + "\"";
|
||||
}
|
||||
|
||||
this.run("sh", ["-c", command], true);
|
||||
var fd = vimperator.fopen(fileout, "<");
|
||||
if (!fd)
|
||||
return null;
|
||||
|
||||
var s = fd.read();
|
||||
fd.close();
|
||||
fileout.remove(false);
|
||||
if (filein)
|
||||
filein.remove(false);
|
||||
|
||||
return s;
|
||||
},
|
||||
|
||||
// files which end in .js are sourced as pure javascript files,
|
||||
// no need (actually forbidden) to add: js <<EOF ... EOF around those files
|
||||
source: function(filename, silent)
|
||||
{
|
||||
filename = expandPath(filename);
|
||||
|
||||
try
|
||||
{
|
||||
var fd = vimperator.fopen(filename, "<");
|
||||
if (!fd)
|
||||
return;
|
||||
|
||||
var s = fd.read();
|
||||
fd.close();
|
||||
|
||||
// handle pure javascript files specially
|
||||
if (filename.search("\.js$") != -1)
|
||||
{
|
||||
eval(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
var heredoc = "";
|
||||
var heredoc_end = null; // the string which ends the heredoc
|
||||
s.split("\n").forEach(function(line)
|
||||
{
|
||||
if (heredoc_end) // we already are in a heredoc
|
||||
{
|
||||
if (heredoc_end.test(line))
|
||||
{
|
||||
eval(heredoc);
|
||||
heredoc = "";
|
||||
heredoc_end = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
heredoc += line + "\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// check for a heredoc
|
||||
var [count, cmd, special, args] = vimperator.commands.parseCommand(line);
|
||||
var command = vimperator.commands.get(cmd);
|
||||
if (command && command.name == "javascript")
|
||||
{
|
||||
var matches = args.match(/(.*)<<\s*([^\s]+)$/);
|
||||
if (matches)
|
||||
{
|
||||
heredoc_end = new RegExp("^" + matches[2] + "$", "m");
|
||||
if (matches[1])
|
||||
heredoc = matches[1] + "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
command.execute(args, special, count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// execute a normal vimperator command
|
||||
vimperator.execute(line);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
vimperator.log("Sourced: " + filename, 3);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
if (!silent)
|
||||
vimperator.echoerr(e);
|
||||
}
|
||||
},
|
||||
|
||||
startup: function()
|
||||
{
|
||||
window.dump("Vimperator startup\n");
|
||||
vimperator.log("Initializing vimperator object...", 1);
|
||||
|
||||
// these objects are created here only after the chrome is ready
|
||||
vimperator.log("Loading module options...", 3);
|
||||
vimperator.options = new Options();
|
||||
vimperator.log("Loading module events...", 3);
|
||||
vimperator.events = new Events();
|
||||
vimperator.log("Loading module commands...", 3);
|
||||
vimperator.commands = new Commands();
|
||||
vimperator.log("Loading module bookmarks...", 3);
|
||||
vimperator.bookmarks = new Bookmarks();
|
||||
vimperator.log("Loading module history...", 3);
|
||||
vimperator.history = new History();
|
||||
vimperator.log("Loading module commandline...", 3);
|
||||
vimperator.commandline = new CommandLine();
|
||||
vimperator.log("Loading module search...", 3);
|
||||
vimperator.search = new Search();
|
||||
vimperator.log("Loading module preview window...", 3);
|
||||
vimperator.previewwindow = new InformationList("vimperator-previewwindow", { incremental_fill: false, max_items: 10 });
|
||||
vimperator.log("Loading module buffer window...", 3);
|
||||
vimperator.bufferwindow = new InformationList("vimperator-bufferwindow", { incremental_fill: false, max_items: 10 });
|
||||
vimperator.log("Loading module mappings...", 3);
|
||||
vimperator.mappings = new Mappings();
|
||||
vimperator.log("Loading module statusline...", 3);
|
||||
vimperator.statusline = new StatusLine();
|
||||
vimperator.log("Loading module buffer...", 3);
|
||||
vimperator.buffer = new Buffer();
|
||||
vimperator.log("Loading module editor...", 3);
|
||||
vimperator.editor = new Editor();
|
||||
vimperator.log("Loading module tabs...", 3);
|
||||
vimperator.tabs = new Tabs();
|
||||
vimperator.log("Loading module marks...", 3);
|
||||
vimperator.marks = new Marks();
|
||||
vimperator.log("Loading module quickmarks...", 3);
|
||||
vimperator.quickmarks = new QuickMarks();
|
||||
vimperator.log("Loading module hints...", 3);
|
||||
vimperator.hints = new Hints();
|
||||
vimperator.log("All modules loaded", 3);
|
||||
|
||||
vimperator.echo = vimperator.commandline.echo;
|
||||
vimperator.echoerr = vimperator.commandline.echoErr;
|
||||
|
||||
vimperator.globalVariables = {};
|
||||
|
||||
// TODO: move elsewhere
|
||||
vimperator.registerCallback("submit", vimperator.modes.EX, function(command) { vimperator.execute(command); } );
|
||||
vimperator.registerCallback("complete", vimperator.modes.EX, function(str) { return vimperator.completion.exTabCompletion(str); } );
|
||||
|
||||
// first time intro message
|
||||
if (Options.getPref("firsttime", true))
|
||||
{
|
||||
setTimeout(function() {
|
||||
vimperator.help(null, null, null, { inTab: true });
|
||||
Options.setPref("firsttime", false);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// disable caret browsing initially
|
||||
//Options.setFirefoxPref("accessibility.browsewithcaret", false);
|
||||
//vimperator.focusContent();
|
||||
|
||||
// always start in normal mode
|
||||
vimperator.modes.reset();
|
||||
|
||||
// finally, read a ~/.vimperatorrc
|
||||
// make sourcing asynchronous, otherwise commands that open new tabs won't work
|
||||
setTimeout(function() {
|
||||
|
||||
var rc_file = getRCFile();
|
||||
|
||||
if (rc_file)
|
||||
vimperator.source(rc_file, true);
|
||||
else
|
||||
vimperator.log("No user RC file found", 3);
|
||||
|
||||
// also source plugins in ~/.vimperator/plugin/
|
||||
var entries = [];
|
||||
try
|
||||
{
|
||||
var plugin_dir = getPluginDir();
|
||||
|
||||
if (plugin_dir)
|
||||
{
|
||||
var fd = vimperator.fopen(plugin_dir);
|
||||
var entries = fd.read();
|
||||
fd.close();
|
||||
vimperator.log("Sourcing plugin directory...", 3);
|
||||
entries.forEach(function(file) {
|
||||
if (!file.isDirectory())
|
||||
vimperator.source(file.path, false);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
vimperator.log("No user plugin directory found", 3);
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// thrown if directory does not exist
|
||||
//vimperator.log("Error sourcing plugin directory: " + e);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
vimperator.log("Vimperator fully initialized", 1);
|
||||
},
|
||||
|
||||
shutdown: function()
|
||||
{
|
||||
window.dump("Vimperator shutdown\n");
|
||||
|
||||
// save our preferences
|
||||
vimperator.commandline.destroy();
|
||||
vimperator.quickmarks.destroy();
|
||||
vimperator.options.destroy();
|
||||
vimperator.events.destroy();
|
||||
|
||||
window.dump("All vimperator modules destroyed\n");
|
||||
},
|
||||
|
||||
sleep: function(ms)
|
||||
{
|
||||
var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
|
||||
var mainThread = threadManager.mainThread;
|
||||
|
||||
var then = new Date().getTime(), now = then;
|
||||
for (; now - then < ms; now = new Date().getTime()) {
|
||||
mainThread.processNextEvent(true);
|
||||
}
|
||||
}
|
||||
|
||||
} //}}}
|
||||
})(); //}}}
|
||||
|
||||
// called when the chrome is fully loaded and before the main window is shown
|
||||
window.addEventListener("load", vimperator.startup, false);
|
||||
window.addEventListener("unload", vimperator.shutdown, false);
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
173
content/vimperator.xul
Normal file
173
content/vimperator.xul
Normal file
@@ -0,0 +1,173 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!-- ***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2007 Martin Stubenschrott
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK ***** -->
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
|
||||
|
||||
<!-- The stylesheet which is used for the :help command -->
|
||||
<?xml-stylesheet href="chrome://vimperator/skin/vimperator.css" type="text/css"?>
|
||||
|
||||
<overlay id="vimperator"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:nc="http://home.netscape.com/NC-rdf#"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/x-javascript;version=1.7" src="vimperator.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="bookmarks.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="buffers.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="commands.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="completion.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="editor.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="events.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="file.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="find.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="help.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="hints.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="mappings.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="modes.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="options.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="tabs.js"/>
|
||||
<script type="application/x-javascript;version=1.7" src="ui.js"/>
|
||||
|
||||
<window id="main-window">
|
||||
|
||||
<keyset id="mainKeyset">
|
||||
<key id="key_open_vimbar" key=":" oncommand="vimperator.commandline.open(':', '', vimperator.modes.EX);" modifiers=""/>
|
||||
<key id="key_stop" keycode="VK_ESCAPE" oncommand="vimperator.events.onEscape();"/>
|
||||
<!-- other keys are handled inside vimperator.js event loop -->
|
||||
</keyset>
|
||||
|
||||
<panel id="vimperator-visualbell"/>
|
||||
|
||||
<!--this notifies us also of focus events in the XUL
|
||||
from: http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands !-->
|
||||
<commandset id="onVimperatorFocus"
|
||||
commandupdater="true"
|
||||
events="focus"
|
||||
oncommandupdate="vimperator.events.onFocusChange(event);"/>
|
||||
<commandset id="onVimperatorSelect"
|
||||
commandupdater="true"
|
||||
events="select"
|
||||
oncommandupdate="vimperator.events.onSelectionChange(event);"/>
|
||||
|
||||
<statusbar id="status-bar">
|
||||
<hbox insertbefore="statusbar-display" id="vimperator-statusline" flex="1" height="10" hidden="false" align="center">
|
||||
<textbox class="plain" id="vimperator-statusline-field-url" readonly="false" flex="1" crop="end"/>
|
||||
<label class="plain" id="vimperator-statusline-field-inputbuffer" flex="0"/>
|
||||
<label class="plain" id="vimperator-statusline-field-progress" flex="0"/>
|
||||
<label class="plain" id="vimperator-statusline-field-tabcount" flex="0"/>
|
||||
<label class="plain" id="vimperator-statusline-field-bufferposition" flex="0"/>
|
||||
</hbox>
|
||||
<!-- just hide them so other elements since other elements expect them -->
|
||||
<statusbarpanel id="statusbar-display" hidden="true"/>
|
||||
<statusbarpanel id="statusbar-progresspanel" hidden="true"/>
|
||||
</statusbar>
|
||||
|
||||
<vbox id="vimperator-container" hidden="false">
|
||||
<listbox id="vimperator-bufferwindow" class="plain" rows="10" flex="1" hidden="true"
|
||||
onclick= "vimperator.bufferwindow.onEvent(event);"
|
||||
ondblclick="vimperator.bufferwindow.onEvent(event);"
|
||||
onkeydown= "vimperator.bufferwindow.onEvent(event);">
|
||||
<listcols>
|
||||
<listcol flex="1" width="50%"/>
|
||||
<listcol flex="1" width="50%"/>
|
||||
</listcols>
|
||||
</listbox>
|
||||
|
||||
<listbox id="vimperator-previewwindow" class="plain" rows="10" flex="1" hidden="true"
|
||||
onclick= "vimperator.previewwindow.onEvent(event);"
|
||||
ondblclick="vimperator.previewwindow.onEvent(event);"
|
||||
onkeydown= "vimperator.previewwindow.onEvent(event);">
|
||||
<listcols>
|
||||
<listcol flex="1" width="50%"/>
|
||||
<listcol flex="1" width="50%"/>
|
||||
</listcols>
|
||||
</listbox>
|
||||
|
||||
<listbox id="vimperator-completion" class="plain" rows="1" flex="1" hidden="true">
|
||||
<listcols>
|
||||
<listcol flex="1" width="50%"/>
|
||||
<listcol flex="1" width="50%"/>
|
||||
</listcols>
|
||||
</listbox>
|
||||
|
||||
<iframe id="vimperator-multiline-output" src="about:blank" flex="1" height="10px" hidden="false" collapsed="true"
|
||||
onclick="vimperator.commandline.onMultilineOutputEvent(event)"/>
|
||||
|
||||
<hbox id="vimperator-commandline" hidden="false">
|
||||
<label class="plain" id="vimperator-commandline-prompt" flex="0" crop="end" value="" collapsed="true"/>
|
||||
<textbox class="plain" id="vimperator-commandline-command" flex="1" hidden="false" type="timed" timeout="100"
|
||||
onkeypress="vimperator.commandline.onEvent(event);"
|
||||
oninput="vimperator.commandline.onEvent(event);"
|
||||
onfocus="vimperator.commandline.onEvent(event);"
|
||||
onblur="vimperator.commandline.onEvent(event);"/>
|
||||
</hbox>
|
||||
|
||||
<textbox id="vimperator-multiline-input" class="plain" flex="1" rows="1" hidden="false" collapsed="true" multiline="true"
|
||||
onkeypress="vimperator.commandline.onMultilineInputEvent(event);"
|
||||
oninput="vimperator.commandline.onMultilineInputEvent(event);"
|
||||
onblur="vimperator.commandline.onMultilineInputEvent(event);"/>
|
||||
</vbox>
|
||||
|
||||
</window>
|
||||
|
||||
<menupopup id="viewSidebarMenu">
|
||||
<menuitem observes="vimperator-viewAddonsSidebar" label="Add-ons" accesskey="A"/>
|
||||
<menuitem observes="vimperator-viewPreferencesSidebar" label="Preferences" accesskey="P"/>
|
||||
<menuitem observes="vimperator-viewDownloadsSidebar" label="Downloads" accesskey="D"/>
|
||||
</menupopup>
|
||||
|
||||
<broadcasterset id="mainBroadcasterSet">
|
||||
<broadcaster id="vimperator-viewAddonsSidebar"
|
||||
autoCheck="false"
|
||||
type="checkbox"
|
||||
group="sidebar"
|
||||
sidebarurl="chrome://mozapps/content/extensions/extensions.xul"
|
||||
sidebartitle="Add-ons"
|
||||
oncommand="toggleSidebar('vimperator-viewAddonsSidebar');"/>
|
||||
<broadcaster id="vimperator-viewPreferencesSidebar"
|
||||
autoCheck="false"
|
||||
type="checkbox"
|
||||
group="sidebar"
|
||||
sidebarurl="about:config"
|
||||
sidebartitle="Preferences"
|
||||
oncommand="toggleSidebar('vimperator-viewPreferencesSidebar');"/>
|
||||
<broadcaster id="vimperator-viewDownloadsSidebar"
|
||||
autoCheck="false"
|
||||
type="checkbox"
|
||||
group="sidebar"
|
||||
sidebarurl="chrome://mozapps/content/downloads/downloads.xul"
|
||||
sidebartitle="Downloads"
|
||||
oncommand="toggleSidebar('vimperator-viewDownloadsSidebar');"/>
|
||||
</broadcasterset>
|
||||
|
||||
</overlay>
|
||||
|
||||
<!-- vim: set fdm=marker sw=4 ts=4 et: -->
|
||||
Reference in New Issue
Block a user