mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2025-12-30 08:22:27 +01:00
change directory structure to follow the more standard package hierarchy
This commit is contained in:
879
content/bookmarks.js
Normal file
879
content/bookmarks.js
Normal file
@@ -0,0 +1,879 @@
|
||||
/***** 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 search_service = Components.classes["@mozilla.org/browser/search-service;1"].
|
||||
getService(Components.interfaces.nsIBrowserSearchService);
|
||||
const rdf_service = Components.classes["@mozilla.org/rdf/rdf-service;1"].
|
||||
getService( Components.interfaces.nsIRDFService );
|
||||
|
||||
var bookmarks = null;
|
||||
var keywords = null;
|
||||
|
||||
if (vimperator.options["preload"])
|
||||
setTimeout(function() { load(); } , 100);
|
||||
|
||||
function load()
|
||||
{
|
||||
// update our bookmark cache
|
||||
var root = rdf_service.GetResource("NC:BookmarksRoot");
|
||||
bookmarks = []; // also clear our bookmark cache
|
||||
keywords = [];
|
||||
|
||||
var bmarks = []; // here getAllChildren will store the bookmarks
|
||||
BookmarksUtils.getAllChildren(root, bmarks);
|
||||
|
||||
// getAllChildren(root) ignores the BTF
|
||||
// NOTE: there's probably a better way to do this...
|
||||
var btf_bmarks = [];
|
||||
BookmarksUtils.getAllChildren(BMSVC.getBookmarksToolbarFolder(), btf_bmarks);
|
||||
|
||||
bmarks = bmarks.concat(btf_bmarks);
|
||||
|
||||
for (var i = 0; i < bmarks.length; i++)
|
||||
{
|
||||
if (bmarks[i][0] && bmarks[i][1])
|
||||
bookmarks.push([bmarks[i][1].Value, bmarks[i][0].Value ]);
|
||||
|
||||
// keyword
|
||||
if (bmarks[i][1] && bmarks[i][2])
|
||||
keywords.push([bmarks[i][2].Value, bmarks[i][0].Value, bmarks[i][1].Value]);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// FIXME: add filtering here rather than having to calling
|
||||
// get_bookmark_completions()
|
||||
this.get = function()
|
||||
{
|
||||
if (!bookmarks)
|
||||
load();
|
||||
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
// TODO: keyword support
|
||||
this.add = function (title, uri, keyword)
|
||||
{
|
||||
if (!bookmarks)
|
||||
load();
|
||||
|
||||
folder = rdf_service.GetResource("NC:BookmarksRoot");
|
||||
var rSource = BookmarksUtils.createBookmark(title, uri, keyword, title);
|
||||
var selection = BookmarksUtils.getSelectionFromResource(rSource);
|
||||
var target = BookmarksUtils.getTargetFromFolder(folder);
|
||||
BookmarksUtils.insertAndCheckSelection("newbookmark", selection, target);
|
||||
|
||||
//also update bookmark cache
|
||||
bookmarks.unshift([uri, title]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: no idea what it does, it Just Works (TM)
|
||||
// returns number of deleted bookmarks
|
||||
this.remove = function(url)
|
||||
{
|
||||
var deleted = 0;
|
||||
if (!url)
|
||||
return 0;
|
||||
|
||||
// gNC_NS for trunk, NC_NS for 1.X
|
||||
//try { var pNC_NS; pNC_NS = gNC_NS;} catch (err) { pNC_NS = NC_NS;}
|
||||
if (!BMSVC || !BMDS || !RDF || !gNC_NS) // defined from firefox
|
||||
return 0;
|
||||
|
||||
var curfolder = RDF.GetResource("NC:BookmarksRoot");
|
||||
var urlArc = RDF.GetResource(gNC_NS + "URL");
|
||||
var urlLiteral = RDF.GetLiteral(url);
|
||||
if (BMDS.hasArcIn(urlLiteral, urlArc))
|
||||
{
|
||||
var bmResources, bmResource, title, uri, type, ptype;
|
||||
bmResources = BMSVC.GetSources(urlArc, urlLiteral, true);
|
||||
while (bmResources.hasMoreElements())
|
||||
{
|
||||
bmResource = bmResources.getNext();
|
||||
type = BookmarksUtils.resolveType(bmResource);
|
||||
if (type != "ImmutableBookmark")
|
||||
{
|
||||
ptype = BookmarksUtils.resolveType(BMSVC.getParent(bmResource));
|
||||
// alert(type);
|
||||
// if ( type == "Folder") // store the current folder
|
||||
// curfolder = bmResource;
|
||||
if ( (type == "Bookmark" || type == "IEFavorite") && ptype != "Livemark")
|
||||
{
|
||||
title = BookmarksUtils.getProperty(bmResource, gNC_NS + "Name");
|
||||
uri = BookmarksUtils.getProperty(bmResource, gNC_NS + "URL");
|
||||
|
||||
if (uri == url)
|
||||
{
|
||||
RDFC.Init(BMDS, BMSVC.getParent(bmResource));
|
||||
RDFC.RemoveElement(bmResource, true);
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// also update bookmark cache, if we removed at least one bookmark
|
||||
if (deleted > 0)
|
||||
load();
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
// 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><tr align=\"left\" style=\"color: magenta\"><th>title</th><th>URL</th></tr>";
|
||||
for (var i = 0; i < items.length; i++)
|
||||
{
|
||||
list += "<tr><td>" + items[i][1] + "</td><td style=\"color: green\">" + items[i][0] + "</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 rdf_service = Components.classes["@mozilla.org/rdf/rdf-service;1"].
|
||||
getService( Components.interfaces.nsIRDFService );
|
||||
const global_history_service = Components.classes["@mozilla.org/browser/global-history;2"].
|
||||
getService(Components.interfaces.nsIRDFDataSource);
|
||||
|
||||
var history = null;
|
||||
|
||||
if (vimperator.options["preload"])
|
||||
setTimeout(function() { load(); } , 100);
|
||||
|
||||
function load()
|
||||
{
|
||||
history = [];
|
||||
|
||||
var historytree = document.getElementById("hiddenHistoryTree");
|
||||
if (!historytree)
|
||||
return;
|
||||
|
||||
if (historytree.hidden)
|
||||
{
|
||||
historytree.hidden = false;
|
||||
historytree.database.AddDataSource(global_history_service);
|
||||
}
|
||||
|
||||
if (!historytree.ref)
|
||||
historytree.ref = "NC:HistoryRoot";
|
||||
|
||||
var nameResource = rdf_service.GetResource(gNC_NS + "Name");
|
||||
var builder = historytree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
|
||||
|
||||
var count = historytree.view.rowCount;
|
||||
for (var i = count - 1; i >= 0; i--)
|
||||
{
|
||||
var res = builder.getResourceAtIndex(i);
|
||||
var url = res.Value;
|
||||
var title;
|
||||
var titleRes = historytree.database.GetTarget(res, nameResource, true);
|
||||
if (!titleRes)
|
||||
continue;
|
||||
|
||||
var titleLiteral = titleRes.QueryInterface(Components.interfaces.nsIRDFLiteral);
|
||||
if (titleLiteral)
|
||||
title = titleLiteral.Value;
|
||||
else
|
||||
title = "";
|
||||
|
||||
history.push([url, title]);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// FIXME: add filtering here rather than having to call
|
||||
// get_bookmark_completions()
|
||||
this.get = function()
|
||||
{
|
||||
if (!history)
|
||||
load();
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
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?
|
||||
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><tr align=\"left\" style=\"color: magenta\"><th>title</th><th>URL</th></tr>";
|
||||
for (var i = 0; i < items.length; i++)
|
||||
{
|
||||
list += "<tr><td>" + items[i][1] + "</td><td>" + items[i][0] + "</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>" + marks[i][1].location + "</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>" + marks[i][1] + "</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:
|
||||
384
content/buffers.js
Normal file
384
content/buffers.js
Normal file
@@ -0,0 +1,384 @@
|
||||
/***** 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_manager = ZoomManager.prototype.getInstance();
|
||||
const ZOOM_INTERVAL = 25;
|
||||
|
||||
// initialize the zoom levels
|
||||
zoom_manager.zoomFactors = [zoom_manager.MIN];
|
||||
for (var i = ZOOM_INTERVAL; i <= zoom_manager.MAX; i += ZOOM_INTERVAL)
|
||||
zoom_manager.zoomFactors.push(i);
|
||||
|
||||
function setZoom(value)
|
||||
{
|
||||
try
|
||||
{
|
||||
zoom_manager.textZoom = value;
|
||||
vimperator.echo("Text zoom: " + zoom_manager.textZoom + "%");
|
||||
// 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();
|
||||
}
|
||||
catch (e) // Components.results.NS_ERROR_INVALID_ARG
|
||||
{
|
||||
vimperator.echoerr("Zoom value out of range (" + zoom_manager.MIN + "-" + zoom_manager.MAX + ")");
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: this is only needed as there's currently no way to specify a
|
||||
// multiplier when calling ZM.reduce()/ZM.enlarge(). TODO: see if we can
|
||||
// get this added to ZoomManager
|
||||
function bumpZoomLevel(steps)
|
||||
{
|
||||
var adjusted_zoom = zoom_manager.snap(zoom_manager.textZoom);
|
||||
var current = zoom_manager.indexOf(adjusted_zoom);
|
||||
var next = current + steps;
|
||||
|
||||
var start = 0, end = zoom_manager.zoomFactors.length - 1;
|
||||
|
||||
if ((current == start && steps < 0) || (current == end && steps > 0))
|
||||
{
|
||||
vimperator.beep();
|
||||
return;
|
||||
}
|
||||
|
||||
if (next < start)
|
||||
next = start;
|
||||
else if (next > end)
|
||||
next = end;
|
||||
|
||||
setZoom(zoom_manager.zoomFactors[next]);
|
||||
}
|
||||
|
||||
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 zoom_manager.textZoom;
|
||||
});
|
||||
|
||||
this.__defineSetter__("textZoom", function(value)
|
||||
{
|
||||
setZoom(value);
|
||||
});
|
||||
|
||||
this.__defineGetter__("title", function()
|
||||
{
|
||||
return window.content.document.title;
|
||||
});
|
||||
|
||||
// returns an XPathResult object
|
||||
this.evaluateXPath = function(expression, doc, ordered)
|
||||
{
|
||||
if (!doc)
|
||||
doc = window.content.document;
|
||||
|
||||
var result = doc.evaluate(expression, doc,
|
||||
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;
|
||||
}
|
||||
|
||||
// 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>"
|
||||
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];
|
||||
|
||||
list += "<tr><td align=\"right\"> " + number + "</td><td>" + indicator + "</td><td>" + 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)
|
||||
{
|
||||
bumpZoomLevel(steps);
|
||||
}
|
||||
|
||||
this.zoomOut = function(steps)
|
||||
{
|
||||
bumpZoomLevel(-steps);
|
||||
}
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
1651
content/commands.js
Normal file
1651
content/commands.js
Normal file
File diff suppressed because it is too large
Load Diff
517
content/completion.js
Normal file
517
content/completion.js
Normal file
@@ -0,0 +1,517 @@
|
||||
/***** 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 filter_length = filter.length;
|
||||
//filter = filter.toLowerCase();
|
||||
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)
|
||||
{
|
||||
//alert('if: ' + item);
|
||||
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
|
||||
{
|
||||
//alert('else: ' + item);
|
||||
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 */
|
||||
function build_longest_starting_substring(list, filter) //{{{
|
||||
{
|
||||
var filtered = [];
|
||||
//var filter_length = filter.length;
|
||||
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 filter_length = filter.length;
|
||||
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];
|
||||
}
|
||||
//alert(longest);
|
||||
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($_) {
|
||||
return [$_[0], $_[1]];
|
||||
});
|
||||
var mapped = engines.map(function($_) {
|
||||
return [[$_[0]], $_[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) //{{{
|
||||
{
|
||||
//var completions = [];
|
||||
|
||||
// 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($_) {
|
||||
var path = $_.path;
|
||||
if ($_.isDirectory()) path += separator;
|
||||
return [path, ""];
|
||||
});
|
||||
var mapped = entries.map(function($_) {
|
||||
var path = $_.path;
|
||||
if ($_.isDirectory()) path += separator;
|
||||
return [[path], ""];
|
||||
});
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
//vimperator.log(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_completions = [];
|
||||
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) //{{{
|
||||
{
|
||||
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($_) {
|
||||
return [[$_[0]], $_[1]];
|
||||
});
|
||||
|
||||
return build_longest_common_substring(mapped, filter);
|
||||
}, //}}}
|
||||
|
||||
exTabCompletion: function(str) //{{{
|
||||
{
|
||||
var [count, cmd, special, args] = vimperator.commands.parseCommand(str);
|
||||
var completions = new Array;
|
||||
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)
|
||||
{
|
||||
completions = command.completer.call(this, args);
|
||||
// if (command[0][0] == "open" ||
|
||||
// command[0][0] == "tabopen" ||
|
||||
// command[0][0] == "winopen")
|
||||
// start = str.search(/^:*\d*\w+(\s+|.*\|)/); // up to the last | or the first space
|
||||
// else
|
||||
matches = str.match(/^:*\d*\w+\s+/); // up to the first spaces only
|
||||
start = matches[0].length;
|
||||
}
|
||||
}
|
||||
return [start, completions];
|
||||
} //}}}
|
||||
|
||||
}
|
||||
})(); // }}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
787
content/events.js
Normal file
787
content/events.js
Normal file
@@ -0,0 +1,787 @@
|
||||
/***** 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.setMode(); // trick to reshow the mode in the command line
|
||||
}, 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.addMode(null, vimperator.modes.MENU);
|
||||
}
|
||||
function exitPopupMode()
|
||||
{
|
||||
// gContextMenu is set to NULL by firefox, when a context menu is closed
|
||||
if (!gContextMenu && !active_menubar)
|
||||
vimperator.removeMode(null, vimperator.modes.MENU);
|
||||
}
|
||||
function enterMenuMode()
|
||||
{
|
||||
active_menubar = true;
|
||||
vimperator.addMode(null, vimperator.modes.MENU)
|
||||
}
|
||||
function exitMenuMode()
|
||||
{
|
||||
active_menubar = false;
|
||||
vimperator.removeMode(null, 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
|
||||
setTimeout(vimperator.focusContent, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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>");
|
||||
}
|
||||
|
||||
// global escape handler, is called in ALL modes
|
||||
this.onEscape = function()
|
||||
{
|
||||
if (!vimperator.hasMode(vimperator.modes.ESCAPE_ONE_KEY))
|
||||
{
|
||||
if (vimperator.hasMode(vimperator.modes.ESCAPE_ALL_KEYS))
|
||||
{
|
||||
vimperator.removeMode(null, vimperator.modes.ESCAPE_ALL_KEYS);
|
||||
return;
|
||||
}
|
||||
|
||||
vimperator.setMode(vimperator.modes.NORMAL);
|
||||
vimperator.commandline.clear();
|
||||
vimperator.hints.disableHahMode();
|
||||
vimperator.statusline.updateUrl();
|
||||
vimperator.focusContent();
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
if (vimperator.hasMode(vimperator.modes.MENU))
|
||||
return true;
|
||||
|
||||
// XXX: for now only, later: input mappings if form element focused
|
||||
if (isFormElemFocused())
|
||||
{
|
||||
if (key == "<S-Insert>")
|
||||
{
|
||||
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;
|
||||
event.preventDefault();
|
||||
// prevents additional firefox-clipboard pasting
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// handle Escape-one-key mode (Ctrl-v)
|
||||
if (vimperator.hasMode(vimperator.modes.ESCAPE_ONE_KEY) && !vimperator.hasMode(vimperator.modes.ESCAPE_ALL_KEYS))
|
||||
{
|
||||
vimperator.removeMode(null, vimperator.modes.ESCAPE_ONE_KEY);
|
||||
return true;
|
||||
}
|
||||
// handle Escape-all-keys mode (I)
|
||||
if (vimperator.hasMode(vimperator.modes.ESCAPE_ALL_KEYS))
|
||||
{
|
||||
if (vimperator.hasMode(vimperator.modes.ESCAPE_ONE_KEY))
|
||||
vimperator.removeMode(null, vimperator.modes.ESCAPE_ONE_KEY); // and then let flow continue
|
||||
else if (key == "<Esc>" || key == "<C-[>" || key == "<C-v>")
|
||||
; // let flow continue to handle these keys
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME: proper way is to have a better onFocus handler which also handles events for the XUL
|
||||
if (!vimperator.hasMode(vimperator.modes.COMMAND_LINE) &&
|
||||
isFormElemFocused()) // non insert mode, but e.g. the location bar has focus
|
||||
return true;
|
||||
|
||||
if (vimperator.hasMode(vimperator.modes.COMMAND_LINE) &&
|
||||
vimperator.hasMode(vimperator.modes.WRITE_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.hasMode(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.hasMode(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.hasMode(vimperator.modes.QUICK_HINT))
|
||||
vimperator.hints.disableHahMode();
|
||||
else // ALWAYS mode
|
||||
vimperator.hints.resetHintedElements();
|
||||
vimperator.input.buffer = "";
|
||||
}
|
||||
else if (res == 0 || vimperator.hasMode(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.hasMode(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.hasMode(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.modes.NORMAL, 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.modes.NORMAL, candidate_command).length > 0)
|
||||
{
|
||||
vimperator.input.buffer += key;
|
||||
}
|
||||
else
|
||||
{
|
||||
vimperator.input.buffer = "";
|
||||
vimperator.input.pendingArgMap = null;
|
||||
vimperator.input.pendingMotionMap = null;
|
||||
|
||||
stop = false; // command was not a vimperator command, maybe it is a firefox command
|
||||
|
||||
if (!vimperator.hasMode(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.hasMode(vimperator.modes.ESCAPE_ONE_KEY) || vimperator.hasMode(vimperator.modes.ESCAPE_ALL_KEYS) || 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.setMode(); // trick to reshow the mode in the command line
|
||||
}
|
||||
},
|
||||
|
||||
// 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);
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
// 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:
|
||||
271
content/find.js
Normal file
271
content/find.js
Normal file
@@ -0,0 +1,271 @@
|
||||
/***** 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)
|
||||
setTimeout(function() { vimperator.echoerr("E486: Pattern not found: " + search_pattern); }, 0);
|
||||
|
||||
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;
|
||||
|
||||
if (up)
|
||||
result = getBrowser().fastFind.findPrevious();
|
||||
else
|
||||
result = getBrowser().fastFind.findNext();
|
||||
|
||||
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.setMode(vimperator.modes.NORMAL);
|
||||
vimperator.focusContent();
|
||||
}
|
||||
|
||||
// Called when the search is cancelled - for example if someone presses
|
||||
// escape while typing a search
|
||||
this.searchCanceled = function()
|
||||
{
|
||||
//removeMode(MODE_SEARCH);
|
||||
vimperator.setMode(vimperator.modes.NORMAL);
|
||||
vimperator.focusContent();
|
||||
}
|
||||
|
||||
this.highlight = function(text)
|
||||
{
|
||||
// already highlighted?
|
||||
if (window.content.document.getElementById("__firefox-findbar-search-id"))
|
||||
return;
|
||||
|
||||
if (!text)
|
||||
text = last_search_string;
|
||||
|
||||
// NOTE: gFindBar.setCaseSensitivity() in FF2 does NOT set the
|
||||
// accessibility.typeaheadfind.casesensitive pref as needed by
|
||||
// highlightDoc()
|
||||
gFindBar.mTypeAheadCaseSensitive = case_sensitive ? 1 : 0;
|
||||
gFindBar.highlightDoc("white", "black", text);
|
||||
|
||||
// TODO: seems fast enough for now...just
|
||||
// NOTE: FF2 highlighting spans all have the same id rather than a class attribute
|
||||
(function(win)
|
||||
{
|
||||
for (var i = 0; i < win.frames.length; i++)
|
||||
arguments.callee(win.frames[i])
|
||||
var spans = vimperator.buffer.evaluateXPath('//span[@id="__firefox-findbar-search-id"]', win.document)
|
||||
for (var i = 0; i < spans.snapshotLength; i++)
|
||||
spans.snapshotItem(i).setAttribute("style", vimperator.options["hlsearchstyle"]);
|
||||
})(window.content);
|
||||
|
||||
// recreate selection since _highlightDoc collapses the selection backwards
|
||||
getBrowser().fastFind.findNext();
|
||||
|
||||
// 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:
|
||||
117
content/help.css
Normal file
117
content/help.css
Normal file
@@ -0,0 +1,117 @@
|
||||
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:
|
||||
696
content/hints.js
Normal file
696
content/hints.js
Normal file
@@ -0,0 +1,696 @@
|
||||
/***** 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
|
||||
// only works for FF3.0:
|
||||
//var rect = elem.getClientRects()[0];
|
||||
//if (rect)
|
||||
//{
|
||||
// elem.absoLeft = rect.left;
|
||||
// elem.absoTop = rect.top;
|
||||
//}
|
||||
//return;
|
||||
|
||||
if (typeof(elem.validCoord) != "undefined")
|
||||
{
|
||||
if (elem.validCoord == elem.ownerDocument.validCoords)
|
||||
return;
|
||||
}
|
||||
|
||||
if (elem.offsetParent)
|
||||
{
|
||||
genElemCoords(elem.offsetParent);
|
||||
elem.absoLeft = elem.offsetParent.absoLeft + elem.offsetLeft;
|
||||
elem.absoTop = elem.offsetParent.absoTop + elem.offsetTop;
|
||||
}
|
||||
else if (elem.offsetLeft && elem.offsetTop) // TODO: ugly and broken temporary fix until FF3
|
||||
{
|
||||
elem.absoLeft = elem.offsetLeft;
|
||||
elem.absoTop = elem.offsetTop;
|
||||
}
|
||||
else
|
||||
{
|
||||
elem.absoLeft = 0;
|
||||
elem.absoTop = 0;
|
||||
}
|
||||
elem.validCoord = elem.ownerDocument.validCoords;
|
||||
}
|
||||
|
||||
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"];
|
||||
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
|
||||
}
|
||||
|
||||
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.hasMode(vimperator.modes.ALWAYS_HINT))
|
||||
{
|
||||
vimperator.beep();
|
||||
|
||||
// FIXME: this.disableHahMode(win);
|
||||
vimperator.setMode(vimperator.modes.NORMAL);
|
||||
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
|
||||
*/
|
||||
//function enableHahMode(event, mode)
|
||||
this.enableHahMode = function(mode)
|
||||
{
|
||||
vimperator.setMode(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
|
||||
*/
|
||||
//function disableHahMode(event)
|
||||
this.disableHahMode = function(win)
|
||||
{
|
||||
if (!isHahModeEnabled)
|
||||
return;
|
||||
|
||||
vimperator.setMode(vimperator.modes.NORMAL);
|
||||
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.hasMode(vimperator.modes.ALWAYS_HINT))
|
||||
{
|
||||
state = 0;
|
||||
linkCount = 0;
|
||||
linkNumString = '';
|
||||
isHahModeEnabled = true;
|
||||
|
||||
setTimeout( function() {
|
||||
createHints();
|
||||
showHints(null, 0);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
window.document.addEventListener("DOMContentLoaded", initDoc, null);
|
||||
// FIXME: add resize support
|
||||
//window.addEventListener("resize", onResize, null);
|
||||
} //}}}
|
||||
|
||||
// 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 |
1214
content/mappings.js
Normal file
1214
content/mappings.js
Normal file
File diff suppressed because it is too large
Load Diff
184
content/modes.js
Normal file
184
content/modes.js
Normal file
@@ -0,0 +1,184 @@
|
||||
/***** 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.HINTS:
|
||||
return "HINTS" + ext;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleModeChange(oldmode, newmode)
|
||||
{
|
||||
vimperator.log("switching from mode " + oldmode + " to mode " + newmode, 7);
|
||||
|
||||
switch (oldmode)
|
||||
{
|
||||
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,
|
||||
HINTS: 1 << 1,
|
||||
COMMAND_LINE: 1 << 2,
|
||||
// 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
|
||||
|
||||
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:
|
||||
653
content/options.js
Normal file
653
content/options.js
Normal file
@@ -0,0 +1,653 @@
|
||||
/***** 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 status bar (default on)
|
||||
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(["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(["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(["visualbellstyle", "t_vb"], "string",
|
||||
{
|
||||
short_help: "CSS specification of the visual bell",
|
||||
help: "To hide the visual bell use a value of \"display: none;\" or unset it with <code class=\"command\">:set t_vb=</code>",
|
||||
setter: function(value) { if (!value) value = "display: none;"; Options.setPref("visualbellstyle", value); },
|
||||
default_value: "background-color: black; color: black;"
|
||||
}
|
||||
));
|
||||
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:
|
||||
13
content/test.js
Executable file
13
content/test.js
Executable file
@@ -0,0 +1,13 @@
|
||||
var TestCase = mozlab.mozunit.TestCase;
|
||||
var assert = mozlab.mozunit.assertions;
|
||||
|
||||
var tc = new TestCase('testcase description here');
|
||||
|
||||
tc.tests = {
|
||||
'First test is successful': function() {
|
||||
var vimperator = new Vimperator();
|
||||
assert.isDefined(vimperator);
|
||||
assert.isTrue(true);
|
||||
}
|
||||
}
|
||||
tc.run()
|
||||
1133
content/ui.js
Normal file
1133
content/ui.js
Normal file
File diff suppressed because it is too large
Load Diff
780
content/vimperator.js
Normal file
780
content/vimperator.js
Normal file
@@ -0,0 +1,780 @@
|
||||
/***** 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 /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var modes = {
|
||||
// main modes
|
||||
NONE: 0,
|
||||
NORMAL: 1 << 0,
|
||||
INSERT: 1 << 1,
|
||||
VISUAL: 1 << 2,
|
||||
HINTS: 1 << 3,
|
||||
COMMAND_LINE: 1 << 4,
|
||||
// extended modes
|
||||
EX: 1 << 10,
|
||||
READ_MULTILINE: 1 << 11,
|
||||
WRITE_MULTILINE: 1 << 12,
|
||||
SEARCH_FORWARD: 1 << 13,
|
||||
SEARCH_BACKWARD: 1 << 14,
|
||||
ESCAPE_ONE_KEY: 1 << 15,
|
||||
ESCAPE_ALL_KEYS: 1 << 16,
|
||||
QUICK_HINT: 1 << 17,
|
||||
EXTENDED_HINT: 1 << 18,
|
||||
ALWAYS_HINT: 1 << 19,
|
||||
MENU: 1 << 20 // a popupmenu is active
|
||||
}
|
||||
|
||||
var mode_messages = {};
|
||||
mode_messages[modes.NORMAL] = "";
|
||||
mode_messages[modes.INSERT] = "INSERT";
|
||||
mode_messages[modes.VISUAL] = "VISUAL";
|
||||
mode_messages[modes.HINTS] = "HINTS";
|
||||
mode_messages[modes.ESCAPE_ONE_KEY] = "escape one key";
|
||||
mode_messages[modes.ESCAPE_ALL_KEYS] = "escape all keys";
|
||||
mode_messages[modes.ESCAPE_ONE_KEY | modes.ESCAPE_ALL_KEYS] = "pass one key";
|
||||
mode_messages[modes.QUICK_HINT] = "quick";
|
||||
mode_messages[modes.EXTENDED_HINT] = "extended";
|
||||
mode_messages[modes.ALWAYS_HINT] = "always";
|
||||
//mode_messages[modes.MENU] = "menu"; // not a user visible mode
|
||||
|
||||
var mode = modes.NORMAL;
|
||||
var extended_mode = modes.NONE;
|
||||
|
||||
var callbacks = [];
|
||||
|
||||
// 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);
|
||||
|
||||
function showMode()
|
||||
{
|
||||
if (!vimperator.options["showmode"])
|
||||
return;
|
||||
|
||||
var str_mode = mode_messages[mode];
|
||||
var str_extended = mode_messages[extended_mode];
|
||||
if (!str_mode && !str_extended)
|
||||
{
|
||||
vimperator.echo("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (str_mode && str_extended)
|
||||
str_extended = " (" + str_extended + ")";
|
||||
else
|
||||
{
|
||||
str_extended = "(" + str_extended + ")";
|
||||
str_mode = "";
|
||||
}
|
||||
|
||||
vimperator.echo("-- " + str_mode + str_extended + " --");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
return {
|
||||
|
||||
modes: modes,
|
||||
|
||||
//openflags: { // XXX: maybe move these consts in a subnamespace?
|
||||
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)
|
||||
{
|
||||
for (var i in callbacks)
|
||||
{
|
||||
var [thistype, thismode, thisfunc] = callbacks[i];
|
||||
if (mode == thismode && type == thistype)
|
||||
return thisfunc.call(this, data);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getMode: function()
|
||||
{
|
||||
return [mode, extended_mode];
|
||||
},
|
||||
|
||||
// set current mode
|
||||
// use "null" if you only want to set one of those modes
|
||||
setMode: function(main, extended, silent)
|
||||
{
|
||||
// if a main mode is set, the extended is always cleared
|
||||
if (main)
|
||||
{
|
||||
mode = main;
|
||||
extended_mode = vimperator.modes.NONE;
|
||||
}
|
||||
if (typeof extended === "number")
|
||||
extended_mode = extended;
|
||||
|
||||
if (!silent)
|
||||
showMode();
|
||||
},
|
||||
|
||||
// returns true if "whichmode" is found in either the main or
|
||||
// extended mode
|
||||
hasMode: function(whichmode)
|
||||
{
|
||||
return ((mode & whichmode) || (extended_mode & whichmode) > 0) ? true : false;
|
||||
},
|
||||
|
||||
addMode: function(main, extended)
|
||||
{
|
||||
if (main)
|
||||
mode |= main;
|
||||
if (extended)
|
||||
extended_mode |= extended;
|
||||
|
||||
showMode();
|
||||
},
|
||||
|
||||
// always show the new mode in the statusline
|
||||
removeMode: function(main, extended)
|
||||
{
|
||||
if (main)
|
||||
mode = (mode | main) ^ main;
|
||||
if (extended)
|
||||
extended_mode = (extended_mode | extended) ^ extended;
|
||||
|
||||
showMode();
|
||||
},
|
||||
|
||||
beep: function()
|
||||
{
|
||||
if (vimperator.options["visualbell"])
|
||||
{
|
||||
// flash the visual bell
|
||||
var vbell = document.getElementById("vimperator-visualbell");
|
||||
var box = getBrowser().mPanelContainer.boxObject;
|
||||
|
||||
vbell.style.cssText = vimperator.options["visualbellstyle"];
|
||||
vbell.style.position = "fixed";
|
||||
vbell.style.height = box.height + "px";
|
||||
vbell.style.width = box.width + "px";
|
||||
vbell.style.left = box.x + "px";
|
||||
vbell.style.top = box.y + "px";
|
||||
|
||||
vbell.hidden = false
|
||||
|
||||
setTimeout(function() { vbell.hidden = true; }, 50); // may as well be 0 ;-)
|
||||
}
|
||||
else
|
||||
{
|
||||
sound_service.beep();
|
||||
}
|
||||
},
|
||||
|
||||
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"];
|
||||
}
|
||||
},
|
||||
|
||||
// logs a message to the javascript error console
|
||||
log: function(msg, level)
|
||||
{
|
||||
// if (Options.getPref("verbose") >= level) // FIXME: hangs vimperator, probably timing issue --mst
|
||||
console_service.logStringMessage('vimperator: ' + msg);
|
||||
},
|
||||
|
||||
// logs an object to the javascript error console also prints all
|
||||
// properties of the object
|
||||
logObject: function(object, level)
|
||||
{
|
||||
if (typeof object != 'object')
|
||||
return false;
|
||||
|
||||
var string = object + '::\n';
|
||||
for (var i in object)
|
||||
{
|
||||
var value;
|
||||
try
|
||||
{
|
||||
var value = object[i];
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
|
||||
string += i + ": " + value + "\n";
|
||||
}
|
||||
vimperator.log(string, level);
|
||||
},
|
||||
|
||||
// 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);
|
||||
},
|
||||
|
||||
|
||||
// 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 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);
|
||||
}
|
||||
|
||||
vimperator.focusContent();
|
||||
|
||||
// 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");
|
||||
}
|
||||
} //}}}
|
||||
})(); //}}}
|
||||
|
||||
// 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:
|
||||
159
content/vimperator.xul
Normal file
159
content/vimperator.xul
Normal file
@@ -0,0 +1,159 @@
|
||||
<?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"?>
|
||||
<?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="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="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>
|
||||
|
||||
<!-- TODO: move to browser-stack? -->
|
||||
<box id="vimperator-visualbell" hidden="true"/>
|
||||
|
||||
<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