1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-22 15:48:00 +01:00

add a 'hlsearchstyle' option to allow for match styling and a 'linksearch'

option to restrict page searches to link text
This commit is contained in:
Doug Kearns
2007-09-14 13:24:33 +00:00
parent 1546799e37
commit 6735ac3ad1
4 changed files with 135 additions and 75 deletions

4
NEWS
View File

@@ -2,6 +2,10 @@
2007-xx-xx: 2007-xx-xx:
* version 0.6 * version 0.6
* THIS VERSION ONLY WORKS WITH FIREFOX 3.0 * THIS VERSION ONLY WORKS WITH FIREFOX 3.0
* added 'hlsearchstyle' option to allow for user CSS styling of the
highlighted text strings when 'hlsearch' is set
* added 'linksearch' option to restrict page searches to link text - \U
and \u can be used in the search pattern to override 'linksearch'
* improvements for scrollable -- more -- prompt * improvements for scrollable -- more -- prompt
* changed 'I' key to Ctrl-Q to also work in textboxes * changed 'I' key to Ctrl-Q to also work in textboxes
* sites like msn.com or yahoo.com don't focus search field anymore on keydown * sites like msn.com or yahoo.com don't focus search field anymore on keydown

View File

@@ -26,16 +26,29 @@ 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. the terms of any one of the MPL, the GPL or the LGPL.
}}} ***** END LICENSE BLOCK *****/ }}} ***** END LICENSE BLOCK *****/
// TODO: <ESC> should cancel search highlighting in 'incsearch' mode // 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 // make sure you only create this object when the "vimperator" object is ready
function Search() //{{{ function Search() //{{{
{ {
var self = this; // needed for callbacks since "this" is the "vimperator" object in a callback 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 found = false; // true if the last search was successful
var backwards = false; // currently searching backwards var backwards = false; // currently searching backwards
var lastsearch = ""; // keep track of the last searched string var search_string = ""; // current search string (without modifiers)
var lastsearch_backwards = false; // like "backwards", but for the last search, so if you cancel a search with <esc> this is not set var search_pattern = ""; // current search string (includes modifiers)
var case_sensitive = true; 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 // Event handlers for search - closure is needed
vimperator.registerCallback("change", vimperator.modes.SEARCH_FORWARD, function(command) { self.searchKeyPressed(command); }); vimperator.registerCallback("change", vimperator.modes.SEARCH_FORWARD, function(command) { self.searchKeyPressed(command); });
@@ -46,43 +59,46 @@ function Search() //{{{
vimperator.registerCallback("submit", vimperator.modes.SEARCH_BACKWARD, function(command) { self.searchSubmitted(command); }); vimperator.registerCallback("submit", vimperator.modes.SEARCH_BACKWARD, function(command) { self.searchSubmitted(command); });
vimperator.registerCallback("cancel", vimperator.modes.SEARCH_BACKWARD, function() { self.searchCanceled(); }); vimperator.registerCallback("cancel", vimperator.modes.SEARCH_BACKWARD, function() { self.searchCanceled(); });
// clean the pattern search string of modifiers and set the // set search_string, search_pattern, case_sensitive, links_only
// case-sensitivity flag function processUserPattern(pattern)
function processPattern(pattern)
{ {
// strip off pattern terminator and trailing /junk // strip off pattern terminator and offset
if (backwards) if (backwards)
pattern = pattern.replace(/\?.*/, ""); pattern = pattern.replace(/\?.*/, "");
else else
pattern = pattern.replace(/\/.*/, ""); pattern = pattern.replace(/\/.*/, "");
if (!pattern) search_pattern = pattern;
pattern = lastsearch;
if (/\\C/.test(pattern)) // case sensitivity - \c wins if both modifiers specified
{ if (/\c/.test(pattern))
case_sensitive = true;
pattern = pattern.replace(/\\C/, "");
}
else if (/\\c/.test(pattern))
{
case_sensitive = false; case_sensitive = false;
pattern = pattern.replace(/\\c/, ""); else if (/\C/.test(pattern))
} case_sensitive = true;
else if (vimperator.options["ignorecase"] && vimperator.options["smartcase"] && /[A-Z]/.test(pattern)) else if (vimperator.options["ignorecase"] && vimperator.options["smartcase"] && /[A-Z]/.test(pattern))
{
case_sensitive = true; case_sensitive = true;
}
else if (vimperator.options["ignorecase"]) else if (vimperator.options["ignorecase"])
{
case_sensitive = false; case_sensitive = false;
}
else else
{
case_sensitive = true; case_sensitive = true;
}
return 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 modifiers
pattern = pattern.replace(/(\\)?\\[cCuU]/g, function($0, $1) { return $1 ? $0 : "" });
// remove the modifer escape \
pattern = pattern.replace(/\\(\\[cCuU])/g, '$1')
search_string = pattern;
} }
// Called when the search dialog is asked for // Called when the search dialog is asked for
@@ -108,10 +124,15 @@ function Search() //{{{
this.find = function(str, backwards) this.find = function(str, backwards)
{ {
var fastFind = getBrowser().fastFind; var fastFind = getBrowser().fastFind;
str = processPattern(str);
processUserPattern(str);
fastFind.caseSensitive = case_sensitive; fastFind.caseSensitive = case_sensitive;
found = fastFind.find(str, false) != Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND; found = fastFind.find(search_string, links_only) != Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND;
if (!found)
vimperator.echoerr("E486: Pattern not found: " + search_pattern);
return found; return found;
} }
@@ -119,26 +140,17 @@ function Search() //{{{
this.findAgain = function(reverse) this.findAgain = function(reverse)
{ {
// this hack is needed to make n/N work with the correct string, if // this hack is needed to make n/N work with the correct string, if
// we typed /foo<esc> after the original search // we typed /foo<esc> after the original search. Since searchString is
// TODO: this should also clear the current item highlighting // readonly we have to call find() again to update it.
if (getBrowser().fastFind.searchString != lastsearch) if (getBrowser().fastFind.searchString != last_search_string)
{ this.find(last_search_string, false);
this.clear();
this.find(lastsearch, false);
this.highlight(lastsearch);
}
var leader = lastsearch_backwards ? "?" : "/"; var up = reverse ? !last_search_backwards : last_search_backwards;
setTimeout(function() { var result = getBrowser().fastFind.findAgain(up, links_only);
vimperator.commandline.echo(leader + lastsearch);
}, 10);
var up = reverse ? !lastsearch_backwards : lastsearch_backwards;
var result = getBrowser().fastFind.findAgain(up, false);
if (result == Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND) if (result == Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND)
{ {
vimperator.echoerr("E486: Pattern not found: " + lastsearch); vimperator.echoerr("E486: Pattern not found: " + last_search_pattern);
} }
else if (result == Components.interfaces.nsITypeAheadFind.FIND_WRAPPED) else if (result == Components.interfaces.nsITypeAheadFind.FIND_WRAPPED)
{ {
@@ -149,20 +161,21 @@ function Search() //{{{
vimperator.echoerr("search hit TOP, continuing at BOTTOM"); vimperator.echoerr("search hit TOP, continuing at BOTTOM");
else else
vimperator.echoerr("search hit BOTTOM, continuing at TOP"); vimperator.echoerr("search hit BOTTOM, continuing at TOP");
}, 10); }, 0);
} }
else // just clear the command line if something has been found else
{ {
vimperator.echo(""); 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 // Called when the user types a key in the search dialog. Triggers a find attempt if 'incsearch' is set
this.searchKeyPressed = function(command) this.searchKeyPressed = function(command)
{ {
if (!vimperator.options["incsearch"]) if (vimperator.options["incsearch"])
return;
this.find(command, backwards); this.find(command, backwards);
} }
@@ -173,17 +186,24 @@ function Search() //{{{
if (typeof forced_backward === "boolean") if (typeof forced_backward === "boolean")
backwards = forced_backward; backwards = forced_backward;
// use the last pattern if none specified
if (!command)
command = last_search_pattern;
this.clear(); this.clear();
this.find(command, backwards); this.find(command, backwards);
this.highlight(command);
// need to find again to draw the highlight of the current search last_search_backwards = backwards;
// result over the "highlight all" search results last_search_pattern = command.replace(backwards ? /\?.*/ : /\/.*/, ""); // XXX
// very hacky, but seems to work last_search_string = search_string;
setTimeout(function() { self.findAgain(false); }, 10);
lastsearch_backwards = backwards; // TODO: move to find() when reverse incremental searching is kludged in
lastsearch = command; // need to find again for reverse searching
if (backwards)
setTimeout(function() { self.findAgain(false); }, 0);
if (vimperator.options["hlsearch"])
this.highlight(search_string);
vimperator.modes.set(vimperator.modes.NORMAL, null, true); vimperator.modes.set(vimperator.modes.NORMAL, null, true);
} }
@@ -193,21 +213,39 @@ function Search() //{{{
this.searchCanceled = function() this.searchCanceled = function()
{ {
vimperator.modes.reset(); vimperator.modes.reset();
this.clear(); //vimperator.focusContent();
} }
this.highlight = function(word) // this is not dependent on the value of 'hlsearch'
this.highlight = function(text)
{ {
if (!word) // already highlighted?
word = lastsearch; if (window.content.document.getElementsByClassName("__mozilla-findbar-search").length > 0)
return;
if (!text)
text = last_search_string;
gFindBar._setCaseSensitivity(case_sensitive) gFindBar._setCaseSensitivity(case_sensitive)
gFindBar._highlightDoc("yellow", "black", word); gFindBar._highlightDoc("white", "black", text);
// TODO: seems fast enough for now
var spans = window.content.document.getElementsByClassName("__mozilla-findbar-search")
for (var i = 0; i < spans.length; i++)
spans[i].setAttribute("style", vimperator.options["hlsearchstyle"]);
// recreate selection since _highlightDoc collapses the selection backwards
getBrowser().fastFind.findAgain(false, links_only);
// TODO: remove highlighting from non-link matches (HTML - A/AREA with href attribute; XML - Xlink [type="simple"])
} }
this.clear = function() this.clear = function()
{ {
gFindBar._highlightDoc(); gFindBar._highlightDoc();
// need to manually collapse the selection if the document is not
// highlighted
getBrowser().fastFind.collapseSelection();
} }
} //}}} } //}}}

View File

@@ -994,7 +994,9 @@ function Mappings() //{{{
usage: ["/{pattern}[/]<CR>"], usage: ["/{pattern}[/]<CR>"],
help: "Search forward for the first occurance of <code class=\"argument\">{pattern}</code>.<br/>" + help: "Search forward for the first occurance of <code class=\"argument\">{pattern}</code>.<br/>" +
"When \"\\c\" appears anywhere in the pattern the whole pattern is handled as though <code class=\"option\">'ignorecase'</code> is on. " + "When \"\\c\" appears anywhere in the pattern the whole pattern is handled as though <code class=\"option\">'ignorecase'</code> is on. " +
"\"\\C\" forces case-sensitive matching for the whole pattern." "\"\\C\" forces case-sensitive matching for the whole pattern.<br/>" +
"If \"\\u\" appears in the pattern only the text of links is searched for a match as though <code class=\"option\">'linksearch'</code> is on. " +
"\"\\U\" forces the entire page to be searched for a match."
} }
)); ));
addDefaultMap(new Map([vimperator.modes.NORMAL], ["?"], addDefaultMap(new Map([vimperator.modes.NORMAL], ["?"],
@@ -1003,9 +1005,11 @@ function Mappings() //{{{
short_help: "Search backwards for a pattern", short_help: "Search backwards for a pattern",
usage: ["?{pattern}[?]<CR>"], usage: ["?{pattern}[?]<CR>"],
help: "Search backward for the first occurance of <code class=\"argument\">{pattern}</code>.<br/>" + help: "Search backward for the first occurance of <code class=\"argument\">{pattern}</code>.<br/>" +
"When '\\c' appears anywhere in the pattern the whole pattern is handled as though <code class=\"option\">'ignorecase'</code> is on. " + "When \"\\c\" appears anywhere in the pattern the whole pattern is handled as though <code class=\"option\">'ignorecase'</code> is on. " +
"'\\C' forces case-sensitive matching for the whole pattern.<br/>" + "\"\\C\" forces case-sensitive matching for the whole pattern.<br/>" +
"NOTE: incremental searching currenly only works in the forward direction." "If \"\\u\" appears in the pattern only the text of links is searched for a match as though <code class=\"option\">'linksearch'</code> is on. " +
"\"\\U\" forces the entire page to be searched for a match.<br/>" +
"NOTE: incremental searching currently only works in the forward direction."
} }
)); ));
addDefaultMap(new Map([vimperator.modes.NORMAL], ["n"], addDefaultMap(new Map([vimperator.modes.NORMAL], ["n"],

View File

@@ -387,7 +387,7 @@ function Options() //{{{
)); ));
addOption(new Option(["focusedhintstyle", "fhs"], "string", addOption(new Option(["focusedhintstyle", "fhs"], "string",
{ {
short_help: "CSS specification of focused hints appearance", short_help: "CSS specification of focused hints",
default_value: "z-index:5000; font-family:monospace; font-size:12px; color:ButtonText; background-color:ButtonShadow; " + 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;" "border-color:ButtonShadow; border-width:1px; border-style:solid; padding:0px 1px 0px 1px; position:absolute;"
} }
@@ -421,12 +421,12 @@ function Options() //{{{
)); ));
addOption(new Option(["hintstyle", "hs"], "string", addOption(new Option(["hintstyle", "hs"], "string",
{ {
short_help: "CSS specification of unfocused hints appearance", short_help: "CSS specification of unfocused hints",
default_value: "z-index:5000; font-family:monospace; font-size:12px; color:white; background-color:red; " + 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;" "border-color:ButtonShadow; border-width:0px; border-style:solid; padding:0px 1px 0px 1px; position:absolute;"
} }
)); ));
addOption(new Option(["hinttags"], "string", 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>", short_help: "XPath string of hintable elements activated by <code class=\"mapping\">'f'</code> and <code class=\"mapping\">'F'</code>",
default_value: DEFAULT_HINTTAGS default_value: DEFAULT_HINTTAGS
@@ -439,6 +439,12 @@ function Options() //{{{
default_value: false 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", addOption(new Option(["ignorecase", "ic"], "boolean",
{ {
short_help: "Ignore case in search patterns", short_help: "Ignore case in search patterns",
@@ -460,6 +466,13 @@ function Options() //{{{
default_value: true 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(["maxhints", "mh"], "number", addOption(new Option(["maxhints", "mh"], "number",
{ {
short_help: "Maximum number of simultaneously shown hints", short_help: "Maximum number of simultaneously shown hints",
@@ -580,6 +593,7 @@ function Options() //{{{
addOption(new Option(["visualbell", "vb"], "boolean", addOption(new Option(["visualbell", "vb"], "boolean",
{ {
short_help: "Use visual bell instead of beeping on errors", 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 default_value: false
} }
)); ));