diff --git a/NEWS b/NEWS index aed80af1..6e0a9bed 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ 2007-xx-xx: * version 0.6 * 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 * 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 diff --git a/chrome/content/vimperator/find.js b/chrome/content/vimperator/find.js index 0c896ec7..71fd29c6 100644 --- a/chrome/content/vimperator/find.js +++ b/chrome/content/vimperator/find.js @@ -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. }}} ***** END LICENSE BLOCK *****/ -// TODO: should cancel search highlighting in 'incsearch' mode +// TODO: proper backwards search - implement our own component? +// : implement our own highlighter? +// : frameset pages +// : 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 lastsearch = ""; // keep track of the last searched string - var lastsearch_backwards = false; // like "backwards", but for the last search, so if you cancel a search with this is not set - var case_sensitive = true; + 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 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); }); @@ -46,43 +59,46 @@ function Search() //{{{ vimperator.registerCallback("submit", vimperator.modes.SEARCH_BACKWARD, function(command) { self.searchSubmitted(command); }); vimperator.registerCallback("cancel", vimperator.modes.SEARCH_BACKWARD, function() { self.searchCanceled(); }); - // clean the pattern search string of modifiers and set the - // case-sensitivity flag - function processPattern(pattern) + // set search_string, search_pattern, case_sensitive, links_only + function processUserPattern(pattern) { - // strip off pattern terminator and trailing /junk + // strip off pattern terminator and offset if (backwards) pattern = pattern.replace(/\?.*/, ""); else pattern = pattern.replace(/\/.*/, ""); - if (!pattern) - pattern = lastsearch; + search_pattern = pattern; - if (/\\C/.test(pattern)) - { - case_sensitive = true; - pattern = pattern.replace(/\\C/, ""); - } - else if (/\\c/.test(pattern)) - { + // case sensitivity - \c wins if both modifiers specified + if (/\c/.test(pattern)) 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)) - { case_sensitive = true; - } else if (vimperator.options["ignorecase"]) - { case_sensitive = false; - } else - { 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 @@ -108,10 +124,15 @@ function Search() //{{{ this.find = function(str, backwards) { var fastFind = getBrowser().fastFind; - str = processPattern(str); + + processUserPattern(str); 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; } @@ -119,26 +140,17 @@ function Search() //{{{ this.findAgain = function(reverse) { // this hack is needed to make n/N work with the correct string, if - // we typed /foo after the original search - // TODO: this should also clear the current item highlighting - if (getBrowser().fastFind.searchString != lastsearch) - { - this.clear(); - this.find(lastsearch, false); - this.highlight(lastsearch); - } + // we typed /foo after the original search. Since searchString is + // readonly we have to call find() again to update it. + if (getBrowser().fastFind.searchString != last_search_string) + this.find(last_search_string, false); - var leader = lastsearch_backwards ? "?" : "/"; - setTimeout(function() { - vimperator.commandline.echo(leader + lastsearch); - }, 10); - - var up = reverse ? !lastsearch_backwards : lastsearch_backwards; - var result = getBrowser().fastFind.findAgain(up, false); + var up = reverse ? !last_search_backwards : last_search_backwards; + var result = getBrowser().fastFind.findAgain(up, links_only); if (result == Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND) { - vimperator.echoerr("E486: Pattern not found: " + lastsearch); + vimperator.echoerr("E486: Pattern not found: " + last_search_pattern); } else if (result == Components.interfaces.nsITypeAheadFind.FIND_WRAPPED) { @@ -149,21 +161,22 @@ function Search() //{{{ vimperator.echoerr("search hit TOP, continuing at BOTTOM"); else 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) { - if (!vimperator.options["incsearch"]) - return; - - this.find(command, backwards); + if (vimperator.options["incsearch"]) + this.find(command, backwards); } // Called when the enter key is pressed to trigger a search @@ -173,17 +186,24 @@ function Search() //{{{ 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); - this.highlight(command); - // need to find again to draw the highlight of the current search - // result over the "highlight all" search results - // very hacky, but seems to work - setTimeout(function() { self.findAgain(false); }, 10); + last_search_backwards = backwards; + last_search_pattern = command.replace(backwards ? /\?.*/ : /\/.*/, ""); // XXX + last_search_string = search_string; - lastsearch_backwards = backwards; - lastsearch = command; + // TODO: move to find() when reverse incremental searching is kludged in + // need to find again for reverse searching + if (backwards) + setTimeout(function() { self.findAgain(false); }, 0); + + if (vimperator.options["hlsearch"]) + this.highlight(search_string); vimperator.modes.set(vimperator.modes.NORMAL, null, true); } @@ -192,22 +212,40 @@ function Search() //{{{ // escape while typing a search this.searchCanceled = function() { - vimperator.modes.reset(); - this.clear(); + vimperator.modes.reset(); + //vimperator.focusContent(); } - this.highlight = function(word) + // this is not dependent on the value of 'hlsearch' + this.highlight = function(text) { - if (!word) - word = lastsearch; + // already highlighted? + if (window.content.document.getElementsByClassName("__mozilla-findbar-search").length > 0) + return; + + if (!text) + text = last_search_string; gFindBar._setCaseSensitivity(case_sensitive) - gFindBar._highlightDoc("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() { gFindBar._highlightDoc(); + // need to manually collapse the selection if the document is not + // highlighted + getBrowser().fastFind.collapseSelection(); } } //}}} diff --git a/chrome/content/vimperator/mappings.js b/chrome/content/vimperator/mappings.js index 43fa84be..62641db6 100644 --- a/chrome/content/vimperator/mappings.js +++ b/chrome/content/vimperator/mappings.js @@ -994,7 +994,9 @@ function Mappings() //{{{ usage: ["/{pattern}[/]"], help: "Search forward for the first occurance of {pattern}.
" + "When \"\\c\" appears anywhere in the pattern the whole pattern is handled as though 'ignorecase' is on. " + - "\"\\C\" forces case-sensitive matching for the whole pattern." + "\"\\C\" forces case-sensitive matching for the whole pattern.
" + + "If \"\\u\" appears in the pattern only the text of links is searched for a match as though 'linksearch' is on. " + + "\"\\U\" forces the entire page to be searched for a match." } )); addDefaultMap(new Map([vimperator.modes.NORMAL], ["?"], @@ -1003,9 +1005,11 @@ function Mappings() //{{{ short_help: "Search backwards for a pattern", usage: ["?{pattern}[?]"], help: "Search backward for the first occurance of {pattern}.
" + - "When '\\c' appears anywhere in the pattern the whole pattern is handled as though 'ignorecase' is on. " + - "'\\C' forces case-sensitive matching for the whole pattern.
" + - "NOTE: incremental searching currenly only works in the forward direction." + "When \"\\c\" appears anywhere in the pattern the whole pattern is handled as though 'ignorecase' is on. " + + "\"\\C\" forces case-sensitive matching for the whole pattern.
" + + "If \"\\u\" appears in the pattern only the text of links is searched for a match as though 'linksearch' is on. " + + "\"\\U\" forces the entire page to be searched for a match.
" + + "NOTE: incremental searching currently only works in the forward direction." } )); addDefaultMap(new Map([vimperator.modes.NORMAL], ["n"], diff --git a/chrome/content/vimperator/options.js b/chrome/content/vimperator/options.js index 96758d39..e139136b 100644 --- a/chrome/content/vimperator/options.js +++ b/chrome/content/vimperator/options.js @@ -387,7 +387,7 @@ function Options() //{{{ )); 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; " + "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", { - 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; " + "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 'f' and 'F'", default_value: DEFAULT_HINTTAGS @@ -439,6 +439,12 @@ function Options() //{{{ 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", @@ -460,6 +466,13 @@ function Options() //{{{ 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", { short_help: "Maximum number of simultaneously shown hints", @@ -580,6 +593,7 @@ function Options() //{{{ 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 } ));