diff --git a/chrome/content/vimperator/commandline.js b/chrome/content/vimperator/commandline.js new file mode 100644 index 00000000..40a39f4a --- /dev/null +++ b/chrome/content/vimperator/commandline.js @@ -0,0 +1,575 @@ +// XXX: remove! +function save_history() +{ + set_pref("comp_history", comp_history.join("\n")); +} + +function load_history() +{ + var hist = get_pref("comp_history", ""); + comp_history = hist.split("\n"); +} + +function multiliner(line, prev_match, heredoc) +{ + var end = true; + var match = tokenize_ex(line, prev_match[4]); + if (prev_match[3] === undefined) prev_match[3] = ''; + if (match[4] === null) + { + focusContent(false, true); // also sets tab_index to -1 + execute_command.apply(this, match); + } + else + { + if (match[4] === false) + { + prev_match[3] = prev_match[3].replace(new RegExp('<<\s*' + prev_match[4]), heredoc.replace(/\n$/, '')); + focusContent(false, true); // also sets comp_tab_index to -1 + execute_command.apply(this, prev_match); + prev_match = new Array(5); + prev_match[3] = ''; + heredoc = ''; + } + else + { + end = false; + if (!prev_match[3]) + { + prev_match[0] = match[0]; + prev_match[1] = match[1]; + prev_match[2] = match[2]; + prev_match[3] = match[3]; + prev_match[4] = match[4]; + } + else + { + heredoc += match[3] + '\n'; + } + } + } + return [prev_match, heredoc, end]; +} + + + +/* + * This class is used for prompting of user input and echoing of messages + * + * it consists of a prompt and command field + * be sure to only create objects of this class when the chrome is ready + */ +function CommandLine () +{ + //////////////////////////////////////////////////////////////////////////////// + ////////////////////// PRIVATE SECTION ///////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + const UNINITIALIZED = -2; // notifies us, if we need to start history/tab-completion from the beginning + const HISTORY_SIZE = 500; + + var completionlist = new CompletionList(); + var completions = new Array(); + + var history = new Array(); + var history_index = UNINITIALIZED; + var history_start = ""; + + // for the example command "open sometext| othertext" (| is the cursor pos) + var completion_start_index = 0; // will be 5 because we want to complete arguments for the :open command + var completion_prefix = "" // will be: "open sometext" + var completion_postfix = ""; // will be: " othertext" + + var wild_index = 0; // keep track how often we press in a row + var completion_index = UNINITIALIZED; + + // The prompt for the current command, for example : or /. Can be blank + var prompt_widget = document.getElementById('new-vim-commandbar-prompt'); + // The command bar which contains the current command + var command_widget = document.getElementById('new-vim-commandbar'); + + function setNormalStyle() + { + command_widget.inputField.setAttribute("style","font-family: monospace;"); + } + function setErrorStyle() + { + command_widget.inputField.setAttribute("style", "font-family: monospace; color:white; background-color:red; font-weight: bold"); + } + + // Sets the prompt - for example, : or / + function setPrompt(prompt) + { + if (typeof(prompt) != "string") + prompt = ""; + + prompt_widget.value = prompt; + if (prompt) + { + // Initially (in the xul) the prompt is 'collapsed', this makes + // sure it's visible, then we toggle the display which works better + prompt_widget.style.visibility = 'visible'; + prompt_widget.style.display = 'inline'; + prompt_widget.size = prompt.length; + } + else + { + prompt_widget.style.display = 'none'; + } + } + + // Sets the command - e.g. 'tabopen', 'open http://example.com/' + function setCommand(cmd) + { + command_widget.value = cmd; + } + + function addToHistory(str) + { + // first remove all old history elements which have this string + history = history.filter(function(elem) { + return elem != str; + }); + // add string to the command line history + if (str.length >= 1 && history.push(str) > HISTORY_SIZE) + history.shift(); + } + + //////////////////////////////////////////////////////////////////////////////// + ////////////////////// PUBLIC SECTION ////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + + this.getCommand = function() + { + return command_widget.value; + }; + + /** + * All arguments can be ommited and will be defaulted to "" + */ + this.open = function(prompt, cmd, minor_mode) + { + if (!prompt) + prompt = ""; + if (!cmd) + cmd = ""; + if (minor_mode) + setCurrentMode(minor_mode); + + setNormalStyle(); + setPrompt(prompt); + setCommand(cmd); + history_index = UNINITIALIZED; + completion_index = UNINITIALIZED; + command_widget.focus(); + }; + + this.echo = function(str) + { + setNormalStyle(); + setPrompt(""); + setCommand(str); + }; + + this.echoErr = function(str) + { + setErrorStyle(); + setPrompt(""); + setCommand(str); + }; + + this.clear = function() + { + setPrompt(" "); // looks faster than an empty string + setCommand(""); + setNormalStyle(); + }; + + + this.onEvent = function(event) + { + //var end = false; + var command = this.getCommand(); + + if(event.type == "blur") + { + // when we do a command_widget.focus() we get a blur event immediately, + // so check if the target is the actualy input field + if (event.target == command_widget.inputField) + { + addToHistory(command); + completionlist.hide(); + } + } + else if(event.type == "input") + { + vimperator.triggerCallback("change", command); + } + else if(event.type == "keypress") + { + var key = keyToString(event); + /* user pressed ENTER to carry out a command */ + if (key == "" || key == "" || key == "") + { +// try { +// [prev_match, heredoc, end] = multiliner(command, prev_match, heredoc); +// } catch(e) { +// logObject(e); +// echoerr(e.name + ": " + e.message); +// prev_match = new Array(5); +// heredoc = ''; +// return; +// } +// if (!end) +// command_line.value = ""; + + // the command is saved in the blur() handler + focusContent(); + var res = vimperator.triggerCallback("submit", command); + return res; + } + /* user pressed ESCAPE to cancel this prompt */ + else if (key == "" || key == "") + { + var res = vimperator.triggerCallback("cancel"); + addToHistory(command); + this.clear(); + focusContent(true, true); + return res; + } + + /* user pressed UP or DOWN arrow to cycle history completion */ + else if (key == "" || key == "") + { + //always reset the tab completion if we use up/down keys + completion_index = UNINITIALIZED; + + /* save 'start' position for iterating through the history */ + if (history_index == UNINITIALIZED) + { + history_index = history.length; + history_start = command; + } + + while (history_index >= -1 && history_index <= history.length) + { + key == "" ? history_index-- : history_index++; + if (history_index == history.length) // user pressed DOWN when there is no newer history item + { + setCommand(history_start); + return; + } + // cannot go past history start/end + if (history_index <= -1) + { + history_index = 0; + beep(); + break; + } + if (history_index >= history.length + 1) + { + history_index = history.length; + beep(); + break; + } + + if (history[history_index].indexOf(history_start) == 0) + { + setCommand(history[history_index]); + return; + } + } + beep(); + } + + /* user pressed TAB to get completions of a command */ + else if (key == "" || key == "") + { + //always reset our completion history so up/down keys will start with new values + history_index = UNINITIALIZED; + + // we need to build our completion list first + if (completion_index == UNINITIALIZED) + { + // FIXME: completions.clear(); + completion_start_index = 0; + + completion_index = -1; + wild_index = 0; + + completion_prefix = command.substring(0, command_widget.selectionStart); + completion_postfix = command.substring(command_widget.selectionStart); + var res = vimperator.triggerCallback("complete", completion_prefix); + if (res) + [completion_start_index, completions] = res; + + // Sort the completion list + if (get_pref('wildoptions').match(/\bsort\b/)) + { + completions.sort(function(a, b) { + if (a[0] < b[0]) + return -1; + else if (a[0] > b[0]) + return 1; + else + return 0; + }); + } + } + // we could also return when no completion is found + // but we fall through to the cleanup anyway + if (completions.length == 0) + { + beep(); + // prevent tab from moving to the next field + event.preventDefault(); + event.stopPropagation(); + return; + } + + var wim = get_pref('wildmode').split(/,/); + var has_list = false; + var longest = false; + var full = false; + var wildtype = wim[wild_index++] || wim[wim.length - 1]; + if (wildtype == 'list' || wildtype == 'list:full' || wildtype == 'list:longest') + has_list = true; + else if (wildtype == 'longest' || wildtype == 'list:longest') + longest = true; + else if (wildtype == 'full' || wildtype == 'list:full') + full = true; + + // show the list + if (has_list) + { + completionlist.show(completions); + } + + if (full) + { + if (event.shiftKey) + { + completion_index--; + if(completion_index < -1) + completion_index = completions.length -1; + } + else + { + completion_index++; + if(completion_index >= completions.length) + completion_index = -1; + } + + showStatusbarMessage("match " + (completion_index+1).toString() + " of " + completions.length.toString(), STATUSFIELD_PROGRESS); + // if the list is hidden, this function does nothing + completionlist.selectItem(completion_index); + } + + +// if (longest && completions.length == 1) +// { +// completion_index=0; +// } + + if (completion_index == -1 && !longest) // wrapped around matches, reset command line + { + if (full && completions.length > 1) + { + setCommand(completion_prefix + completion_postfix); + //completion_list.selectedIndex = -1; + } + } + + else + { + if (longest && completions.length > 1) + var compl = get_longest_substring(); + if (full) + var compl = completions[completion_index][0]; + if (completions.length == 1) + var compl = completions[COMMANDS][0]; +//alert(compl) + if (compl) + { + setCommand(command.substring(0, completion_start_index) + compl + completion_postfix); + command_widget.selectionStart = command_widget.selectionEnd = completion_start_index + compl.length; + +// XXX: needed? +// // Start a new completion in the next iteration. Useful for commands like :source +// if (completions.length == 1 && !full) // RFC: perhaps the command can indicate whether the completion should be restarted +// completion_index = UNINITIALIZED; + } + } + + // prevent tab from moving to the next field + event.preventDefault(); + event.stopPropagation(); + } + else if (key == "") + { + // reset the tab completion + completion_index = history_index = UNINITIALIZED; + + // and blur the command line if there is no text left + if(command.length == 0) + { + this.clear(); + focusContent(); + } + } + else // any other key + { + // reset the tab completion + completion_index = history_index = UNINITIALIZED; + } + } + } + logMessage("CommandLine initialized."); +} + +function CompletionList() +{ + const MAX_ITEMS = 10; + const CONTEXT_LINES = 3; + + var completions = null; // a reference to the Array of completions + var completion_widget = document.getElementById("vim-completion"); + var list_offset = 0; // how many items is the displayed list shifted from the internal tab index + var list_index = 0; // list_offset + list_index = completions[item] + + // add a single completion item to the list + function addItem(completion_item, at_beginning) + { + var item = document.createElement("listitem"); + var cell1 = document.createElement("listcell"); + var cell2 = document.createElement("listcell"); + + cell1.setAttribute("label", completion_item[0]); + cell2.setAttribute("label", completion_item[1]); + cell2.setAttribute("style", "color:green; font-family: sans"); + + item.appendChild(cell1); + item.appendChild(cell2); + if (at_beginning == true) + { + var items = completion_widget.getElementsByTagName("listitem"); + if (items.length > 0) + completion_widget.insertBefore(item, items[0]); + else + completion_widget.appendChild(item); + } + else + completion_widget.appendChild(item); + } + + /** + * uses the entries in completions to fill the listbox + * @param startindex: start at this index and show MAX_ITEMS + * @returns the number of items + */ + function fill(startindex) + { + var complength = completions.length; + + // remove all old items first + var items = completion_widget.getElementsByTagName("listitem"); + while (items.length > 0) { completion_widget.removeChild(items[0]);} + + // find start index + if (startindex + MAX_ITEMS > complength) + startindex = complength - MAX_ITEMS; + if (startindex < 0) + startindex = 0; + + list_offset = startindex; + list_index = -1; + + for(i = startindex; i < complength && i < startindex + MAX_ITEMS; i++) + { + addItem(completions[i], false); + } + + return (i-startindex); + } + + //////////////////////////////////////////////////////////////////////////////// + ////////////////////// PUBLIC SECTION ////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// +this.len = function() {alert(completions.length);}; + this.show = function(compl) + { + completions = compl; + fill(0); + + var length = completions.length; + if (length > MAX_ITEMS) + length = MAX_ITEMS; + if (length > 1) + { + completion_widget.setAttribute("rows", length.toString()); + completion_widget.hidden = false; + return true; + } + else + { + completion_widget.hidden = true; + return false; + } + } + + this.hide = function() + { + completion_widget.hidden = true; + } + + /** + * select index, refill list if necessary + */ + this.selectItem = function(index) + { + if(completion_widget.hidden) + return; + + // find start index + var new_offset = 0; + if (index >= list_offset + MAX_ITEMS - CONTEXT_LINES) + new_offset = index - MAX_ITEMS + CONTEXT_LINES + 1; + else if (index <= list_offset + CONTEXT_LINES) + new_offset = index - CONTEXT_LINES; + else + new_offset = list_offset; + + if (new_offset + MAX_ITEMS > completions.length) + new_offset = completions.length - MAX_ITEMS; + if (new_offset < 0) + new_offset = 0; + + // for speed reason: just remove old item, and add the new one at the end of the list + var items = completion_widget.getElementsByTagName("listitem"); + if (new_offset == list_offset + 1) + { + completion_widget.removeChild(items[0]); + addItem(completions[index + CONTEXT_LINES], false); + } + else if (new_offset == list_offset - 1) + { + completion_widget.removeChild(items[items.length-1]); + addItem(completions[index - CONTEXT_LINES], true); + } + else if (new_offset == list_offset) + { + // do nothing + } + else + fill(new_offset); + + list_offset = new_offset; + completion_widget.selectedIndex = index - list_offset; + } +} + +// function PreviewWindow() +// { +// var completion_widget = document.getElementById("vim-preview_window"); +// } +// PreviewWindow.protoype = new CompletionList; +// var pw = new PreviewWindow(); diff --git a/chrome/content/vimperator/find.js b/chrome/content/vimperator/find.js new file mode 100644 index 00000000..8be77674 --- /dev/null +++ b/chrome/content/vimperator/find.js @@ -0,0 +1,472 @@ +/***** 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. + +The Initial Developer of the Original Code is Shawn Betts. +Portions created by the Initial Developer are Copyright (C) 2004,2005 +by the Initial Developer. All Rights Reserved. + +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 *****/ + +// Finder for vimperator +// Author: Nigel McNie +// Original Author: Shawn Betts +// +// The algorithm was taken from conkeror , but +// extensively refactored and changed to behave like vim (naturally!) + +// The window to search in (which frame) +//var gWin = null; +//var gSelCtrl = null; + +//function highlight(range, node) { +// var startContainer = range.startContainer; +// var startOffset = range.startOffset; +// var endOffset = range.endOffset; +// var docfrag = range.extractContents(); +// var before = startContainer.splitText(startOffset); +// var parent = before.parentNode; +// node.appendChild(docfrag); +// parent.insertBefore(node, before); +// return node; +//} + +// Clears the current selection +// @todo this should be in vimperator.js, and not depend on searcher.gSelCtrl +function clearSelection() { + //var selctrl = gSelCtrl; + var selctrl = vimperator.search.gSelCtrl; + var sel = selctrl.getSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL); + sel.removeAllRanges(); +} + +// Sets what is currently selected +// @todo as for clearSelection +function setSelection(range) { + try { + var selctrlcomp = Components.interfaces.nsISelectionController; + //var selctrl = gSelCtrl; + var selctrl = vimperator.search.gSelCtrl; + var sel = selctrl.getSelection(selctrlcomp.SELECTION_NORMAL); + sel.removeAllRanges(); + sel.addRange(range.cloneRange()); + + selctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL, + selctrlcomp.SELECTION_FOCUS_REGION, + true); + } + catch (e) { + alert("setSelection: " + e); + } +} + +// Highlight find matches and move selection to the first occurrence +// starting from pt. +// @todo move into searcher and clean up +function highlightFind(str, color, wrapped, dir, pt) +{ + try { + var gWin = vimperator.search.gWin;//document.commandDispatcher.focusedWindow; + if (!gWin) { + alert('gWin does not exist here...'); + alert(vimperator.search.gWin); + } + var doc = gWin.document; + var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"].createInstance() + .QueryInterface(Components.interfaces.nsIFind); + var searchRange; + var startPt; + var endPt; + var body = doc.body; + + finder.findBackwards = !dir; + + searchRange = doc.createRange(); + startPt = doc.createRange(); + endPt = doc.createRange(); + + var count = body.childNodes.length; + + // Search range in the doc + searchRange.setStart(body,0); + searchRange.setEnd(body, count); + + if (!dir) { + if (pt == null) { + startPt.setStart(body, count); + startPt.setEnd(body, count); + } else { + startPt.setStart(pt.startContainer, pt.startOffset); + startPt.setEnd(pt.startContainer, pt.startOffset); + } + endPt.setStart(body, 0); + endPt.setEnd(body, 0); + } else { + if (pt == null) { + startPt.setStart(body, 0); + startPt.setEnd(body, 0); + } else { + startPt.setStart(pt.endContainer, pt.endOffset); + startPt.setEnd(pt.endContainer, pt.endOffset); + } + endPt.setStart(body, count); + endPt.setEnd(body, count); + } + // search the doc + var retRange = null; + var selectionRange = null; + + if (!wrapped) { + do { + retRange = finder.Find(str, searchRange, startPt, endPt); + var keepSearching = false; + if (retRange) { + var sc = retRange.startContainer; + var ec = retRange.endContainer; + var scp = sc.parentNode; + var ecp = ec.parentNode; + var sy1 = abs_point(scp).y; + var ey2 = abs_point(ecp).y + ecp.offsetHeight; + + startPt = retRange.startContainer.ownerDocument.createRange(); + if (!dir) { + startPt.setStart(retRange.startContainer, retRange.startOffset); + startPt.setEnd(retRange.startContainer, retRange.startOffset); + } else { + startPt.setStart(retRange.endContainer, retRange.endOffset); + startPt.setEnd(retRange.endContainer, retRange.endOffset); + } + // We want to find a match that is completely + // visible, otherwise the view will scroll just a + // bit to fit the selection in completely. +// alert ("sy1: " + sy1 + " scry: " + gWin.scrollY); +// alert ("ey2: " + ey2 + " bot: " + (gWin.scrollY + gWin.innerHeight)); + keepSearching = (dir && sy1 < gWin.scrollY) + || (!dir && ey2 >= gWin.scrollY + gWin.innerHeight); + } + } while (retRange && keepSearching); + } else { + retRange = finder.Find(str, searchRange, startPt, endPt); + } + + if (retRange) { + setSelection(retRange); + selectionRange = retRange.cloneRange(); + // highlightAllBut(str, retRange, color); + } else { + + } + + return selectionRange; + } catch(e) { alert('highlightFind:'+e); } +} + +function clearHighlight() +{ + var win = window._content; + var doc = win.document; + if (!document) + return; + + var elem = null; + while ((elem = doc.getElementById("__vimperator-findbar-search-id"))) { + var child = null; + var docfrag = doc.createDocumentFragment(); + var next = elem.nextSibling; + var parent = elem.parentNode; + while((child = elem.firstChild)) { + docfrag.appendChild(child); + } + parent.removeChild(elem); + parent.insertBefore(docfrag, next); + } +} + +/* + * Finds the absolute X and Y co-ordinates of a given node from the top left of + * the document + * + * Taken from conkeror utils.js + */ +function abs_point (node) { + var orig = node; + var pt = {}; + try { + pt.x = node.offsetLeft; + pt.y = node.offsetTop; + + // Find imagemap's coordinates + if (node.tagName == "AREA") { + var coords = node.getAttribute("coords").split(","); + pt.x += Number(coords[0]); + pt.y += Number(coords[1]); + } + + node = node.offsetParent; + + while (node.tagName != "BODY") { + pt.x += node.offsetLeft; + pt.y += node.offsetTop; + node = node.offsetParent; + } + } + catch (e) { + // Ignore + } + return pt; +} + +// Vimperator searcher +// make sure you only create this object when the "vimperator" object is ready +//Vimperator.prototype.search = new function() +function Search() +{ + var self = this; // needed for callbacks since "this" is the "vimperator" object in a callback + this.gWin = null; + this.gSelCtrl = null; + this.gFindState = []; + + // Event handlers for search - closure is needed + vimperator.registerCallback("change", MODE_SEARCH, function(command){ self.searchKeyPressed(command); }); + vimperator.registerCallback("submit", MODE_SEARCH, function(command){ self.searchSubmitted(command); }); + vimperator.registerCallback("cancel", MODE_SEARCH, function(){ self.searchCancelled(); }); + + + // Called when the search dialog is asked for. Sets up everything necessary + // for this round of searching + this.openSearchDialog = function() { + // Get a reference to the focused window if necessary + if (this.gWin == null) this.gWin = document.commandDispatcher.focusedWindow; + + // Change the currently selected text to not be the attention colour + // @todo: check what this REALLY does + try { + this.gSelCtrl = this.getFocusedSelCtrl(); + this.gSelCtrl.setDisplaySelection(Components.interfaces.nsISelectionController.SELECTION_ATTENTION); + this.gSelCtrl.repaintSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL); + } + catch (e) { + alert('Could not change the colour of the current selection:' + e); + } + + // Initialize the state list for this attempt at searching + var state = this.createInitialFindState(); + this.gFindState = []; + this.gFindState.push(state); + this.resumeFindState(state); + + vimperator.commandline.open('/', '', MODE_SEARCH); + } + + // Called when the current search needs to be repeated in the forward + // direction + // @todo will need re-jigging when reverse search comes in + this.findNext = function() { + this.find(this.lastFindState()["search-str"], true, this.lastFindState()["range"]); + this.resumeFindState(this.lastFindState()); + // if there is still a search result + if (this.lastFindState()["range"]) { + if (this.lastFindState()["wrapped"]) { + vimperator.echoerr("search hit BOTTOM, continuing at TOP"); + this.lastFindState()["wrapped"] = false; + } + else { + // TODO: this could probably be done in a nicer way - perhaps + // echoErr could not clobber all of this information somehow? + vimperator.echo('/' + this.lastFindState()["search-str"]); + } + } + } + + // Called when the current search needs to be repeated in the backward + // direction + this.findPrevious = function() { + this.find(this.lastFindState()["search-str"], false, this.lastFindState()["range"]); + this.resumeFindState(this.lastFindState()); + // if there is still a search result + if (this.lastFindState()["range"]) { + if (this.lastFindState()["wrapped"]) { + vimperator.echoerr("search hit TOP, continuing at BOTTOM"); + this.lastFindState()["wrapped"] = false; + } + else { + vimperator.echo('/' + this.lastFindState()["search-str"]); + } + } + } + + // Called when the user types a key in the search dialog. Triggers a find attempt + this.searchKeyPressed = function(command) { + //this.find(command_line.value, true, this.lastFindState()["point"]); + if (command != "") { + this.find(vimperator.commandline.getCommand(), true, this.lastFindState()["point"]); + this.resumeFindState(this.lastFindState()); + } + else { + clearSelection(); + } + } + + // Called when the enter key is pressed to trigger a search + this.searchSubmitted = function(command) { + removeMode(MODE_SEARCH); + if (this.lastFindState()["range"] == null) { + vimperator.echoerr("E492: Pattern not found: " + this.lastFindState()["search-str"]); + } + } + + // Called when the search is cancelled - for example if someone presses + // escape while typing a search + this.searchCancelled = function() { + removeMode(MODE_SEARCH); + clearSelection(); + focusContent(true, true); + } + + + // + // Helper methods + // + + // Turn on the selection in all frames + // @todo to tell the truth, I have no idea what this does + this.getFocusedSelCtrl = function() { + var ds = getBrowser().docShell; + var dsEnum = ds.getDocShellEnumerator(Components.interfaces.nsIDocShellTreeItem.typeContent, + Components.interfaces.nsIDocShell.ENUMERATE_FORWARDS); + while (dsEnum.hasMoreElements()) { + ds = dsEnum.getNext().QueryInterface(Components.interfaces.nsIDocShell); + if (ds.hasFocus) { + var display = ds.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsISelectionDisplay); + if (!display) return null; + return display.QueryInterface(Components.interfaces.nsISelectionController); + } + } + + // One last try + return getBrowser().docShell + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsISelectionDisplay) + .QueryInterface(Components.interfaces.nsISelectionController); + } + + // Creates a default find state + this.createInitialFindState = function() { + var state = new Array(); + state["screenx"] = this.gWin.scrollX; + state["screeny"] = this.gWin.scrollY; + state["search-str"] = ""; + state["wrapped"] = false; + state["point"] = null; + state["range"] = document.createRange(); + state["selection"] = null; + state["direction"] = true; + return state; + } + + // Given a find state, moves the browser to the way it should be in the + // state - highlighting the correct thing and the screen scrolled to the + // correct location + this.resumeFindState = function(state) { + if (state["selection"]) { + setSelection(state["selection"]); + } + else { + clearSelection(); + } + this.gWin.scrollTo(state["screenx"], state["screeny"]); + } + + // Retrieves the current find state that we're in + // @todo rename to currentFindState? + this.lastFindState = function() { + return this.gFindState[this.gFindState.length - 1]; + } + + // Adds a find state to the stack of such states. This is done every time a find is successful + this.addFindState = function(screenX, screenY, searchStr, wrapped, point, range, selection, direction) { + var state = new Array(); + state["screenx"] = screenX; + state["screeny"] = screenY; + state["search-str"] = searchStr; + state["wrapped"] = wrapped; + state["point"] = point; + state["range"] = range; + state["selection"] = selection; + state["direction"] = direction; + this.gFindState.push(state); + } + + // Finds text in a page + this.find = function(str, dir, pt) { + var norecurse = arguments[3]; + + var matchRange; + clearHighlight(); + + // Should we wrap this time? + var wrapped = this.lastFindState()["wrapped"]; + var point = pt; + if (this.lastFindState()["wrapped"] == false + && this.lastFindState()["range"] == null + && this.lastFindState()["search-str"] == str + && this.lastFindState()["direction"] == dir) { + wrapped = true; + point = null; + } + matchRange = highlightFind(str, "lightblue", wrapped, dir, point); + if (matchRange == null) { + // No more matches in this direction. So add the state and then find + // again to wrap around. But only find again once to prevent infinite + // recursion if an error occurs + this.addFindState(this.gWin.scrollX, this.gWin.scrollY, str, wrapped, point, + matchRange, this.lastFindState()["selection"], dir); + if (!norecurse) + this.find(str, dir, pt, true); + } + else { + this.addFindState(this.gWin.scrollX, this.gWin.scrollY, str, wrapped, + point, matchRange, matchRange, dir); + } + } + +//logObject(vimperator); + logMessage("Search initialized."); +} + +//searcher = new searcher(); +//vimperator.registerCallback("submit", MODE_SEARCH, function(command) { /*vimperator.*/alert(command); } ); +//vimperator.registerCallback("change", MODE_SEARCH, function(command) { /*vimperator.*/alert(command); } ); + +// @todo nicer way to register commands? +g_commands.push( + [ + ["nohilight", "noh"], + ["noh[ilight]"], + "Clear the current selection", + "", + clearSelection, + null + ] +);