1
0
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:
Doug Kearns
2007-10-01 10:23:39 +00:00
parent 6b6b6c2c8b
commit 2d60a48fd1
23 changed files with 330 additions and 163 deletions

879
content/bookmarks.js Normal file
View 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>&nbsp;" + 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>&nbsp;&nbsp;&nbsp;&nbsp;" + 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
View 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 = "&nbsp;<span style=\"color: blue\">%</span>&nbsp;";
else if (i == vimperator.tabs.index(vimperator.tabs.alternate))
indicator = "&nbsp;<span style=\"color: blue\">#</span>&nbsp;";
else
indicator = "&nbsp;&nbsp;&nbsp;";
[number, title] = items[i][0].split(/:\s+/, 2);
url = items[i][1];
list += "<tr><td align=\"right\">&nbsp;&nbsp;" + 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

File diff suppressed because it is too large Load Diff

517
content/completion.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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, "&lt;$1");
//usage = usage.replace(/[^b][^r][^\/]>/g, "&gt;");
usage = usage.replace(/&/g, "&amp;");
usage = usage.replace(/</g, "&lt;");
usage = usage.replace(/>/g, "&gt;");
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, "&lt;");
cmd_name = cmd_name.replace(/>/g, "&gt;");
// cmd_name = cmd_name.replace(/"/g, "&quot;");
// cmd_name = cmd_name.replace(/'/g, "&apos;");
// cmd_name = cmd_name.replace(/&/g, "&amp;");
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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

1214
content/mappings.js Normal file

File diff suppressed because it is too large Load Diff

184
content/modes.js Normal file
View 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
View 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 ? "&nbsp;&nbsp;" + name : "no" + name;
list += "<tr><td>" + name + "</td></tr>";
}
else
{
list += "<tr><td>" + "&nbsp;&nbsp;" + 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\">&lt;C-u&gt;</code> or <code class=\"mapping\">&lt;C-d&gt;</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\">&lt;Tab&gt;</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
View 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
View 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

File diff suppressed because it is too large Load Diff

780
content/vimperator.js Normal file
View 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
View 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: -->