gecko/editor/ui/dialogs/content/EdReplace.js

416 lines
14 KiB
JavaScript

/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* ***** 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 Original Code is Mozilla Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Kin Blas <kin@netscape.com>
* Akkana Peck <akkana@netscape.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of 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 ***** */
var gReplaceDialog; // Quick access to document/form elements.
var gFindInst; // nsIWebBrowserFind that we're going to use
var gFindService; // Global service which remembers find params
var gEditor; // the editor we're using
function initDialogObject()
{
// Create gReplaceDialog object and initialize.
gReplaceDialog = {};
gReplaceDialog.findInput = document.getElementById("dialog.findInput");
gReplaceDialog.replaceInput = document.getElementById("dialog.replaceInput");
gReplaceDialog.caseSensitive = document.getElementById("dialog.caseSensitive");
gReplaceDialog.wrap = document.getElementById("dialog.wrap");
gReplaceDialog.searchBackwards = document.getElementById("dialog.searchBackwards");
gReplaceDialog.findNext = document.getElementById("findNext");
gReplaceDialog.replace = document.getElementById("replace");
gReplaceDialog.replaceAndFind = document.getElementById("replaceAndFind");
gReplaceDialog.replaceAll = document.getElementById("replaceAll");
}
function loadDialog()
{
// Set initial dialog field contents.
// Set initial dialog field contents. Use the gFindInst attributes first,
// this is necessary for window.find()
gReplaceDialog.findInput.value = (gFindInst.searchString
? gFindInst.searchString
: gFindService.searchString);
gReplaceDialog.replaceInput.value = gFindService.replaceString;
gReplaceDialog.caseSensitive.checked = (gFindInst.matchCase
? gFindInst.matchCase
: gFindService.matchCase);
gReplaceDialog.wrap.checked = (gFindInst.wrapFind
? gFindInst.wrapFind
: gFindService.wrapFind);
gReplaceDialog.searchBackwards.checked = (gFindInst.findBackwards
? gFindInst.findBackwards
: gFindService.findBackwards);
doEnabling();
}
function onLoad()
{
// Get the xul <editor> element:
var editorElement = window.arguments[0];
// If we don't get the editor, then we won't allow replacing.
gEditor = editorElement.getEditor(editorElement.contentWindow);
if (!gEditor)
{
window.close();
return;
}
// Get the nsIWebBrowserFind service:
gFindInst = editorElement.webBrowserFind;
try {
// get the find service, which stores global find state
gFindService = Components.classes["@mozilla.org/find/find_service;1"]
.getService(Components.interfaces.nsIFindService);
} catch(e) { dump("No find service!\n"); gFindService = 0; }
// Init gReplaceDialog.
initDialogObject();
// Change "OK" to "Find".
//dialog.find.label = document.getElementById("fBLT").getAttribute("label");
// Fill dialog.
loadDialog();
if (gReplaceDialog.findInput.value)
gReplaceDialog.findInput.select();
else
gReplaceDialog.findInput.focus();
}
function onUnload() {
// Disconnect context from this dialog.
gFindReplaceData.replaceDialog = null;
}
function saveFindData()
{
// Set data attributes per user input.
if (gFindService)
{
gFindService.searchString = gReplaceDialog.findInput.value;
gFindService.matchCase = gReplaceDialog.caseSensitive.checked;
gFindService.wrapFind = gReplaceDialog.wrap.checked;
gFindService.findBackwards = gReplaceDialog.searchBackwards.checked;
}
}
function setUpFindInst()
{
gFindInst.searchString = gReplaceDialog.findInput.value;
gFindInst.matchCase = gReplaceDialog.caseSensitive.checked;
gFindInst.wrapFind = gReplaceDialog.wrap.checked;
gFindInst.findBackwards = gReplaceDialog.searchBackwards.checked;
}
function onFindNext()
{
// Transfer dialog contents to the find service.
saveFindData();
// set up the find instance
setUpFindInst();
// Search.
var result = gFindInst.findNext();
if (!result)
{
var bundle = document.getElementById("findBundle");
AlertWithTitle(null, bundle.getString("notFoundWarning"));
SetTextboxFocus(gReplaceDialog.findInput);
gReplaceDialog.findInput.select();
gReplaceDialog.findInput.focus();
return false;
}
return true;
}
function onReplace()
{
if (!gEditor)
return false;
// Does the current selection match the find string?
var selection = gEditor.selection;
var selStr = selection.toString();
var specStr = gReplaceDialog.findInput.value;
if (!gReplaceDialog.caseSensitive.checked)
{
selStr = selStr.toLowerCase();
specStr = specStr.toLowerCase();
}
// Unfortunately, because of whitespace we can't just check
// whether (selStr == specStr), but have to loop ourselves.
// N chars of whitespace in specStr can match any M >= N in selStr.
var matches = true;
var specLen = specStr.length;
var selLen = selStr.length;
if (selLen < specLen)
matches = false;
else
{
var specArray = specStr.match(/\S+|\s+/g);
var selArray = selStr.match(/\S+|\s+/g);
if ( specArray.length != selArray.length)
matches = false;
else
{
for (var i=0; i<selArray.length; i++)
{
if (selArray[i] != specArray[i])
{
if ( /\S/.test(selArray[i][0]) || /\S/.test(specArray[i][0]) )
{
// not a space chunk -- match fails
matches = false;
break;
}
else if ( selArray[i].length < specArray[i].length )
{
// if it's a space chunk then we only care that sel be
// at least as long as spec
matches = false;
break;
}
}
}
}
}
// If the current selection doesn't match the pattern,
// then we want to find the next match, but not do the replace.
// That's what most other apps seem to do.
// So here, just return.
if (!matches)
return false;
// Transfer dialog contents to the find service.
saveFindData();
// For reverse finds, need to remember the caret position
// before current selection
var newRange;
if (gReplaceDialog.searchBackwards.checked && selection.rangeCount > 0)
{
newRange = selection.getRangeAt(0).cloneRange();
newRange.collapse(true);
}
// nsPlaintextEditor::InsertText fails if the string is empty,
// so make that a special case:
var replStr = gReplaceDialog.replaceInput.value;
if (replStr == "")
gEditor.deleteSelection(0);
else
gEditor.insertText(replStr);
// For reverse finds, need to move caret just before the replaced text
if (gReplaceDialog.searchBackwards.checked && newRange)
{
gEditor.selection.removeAllRanges();
gEditor.selection.addRange(newRange);
}
return true;
}
function onReplaceAll()
{
if (!gEditor)
return;
var findStr = gReplaceDialog.findInput.value;
var repStr = gReplaceDialog.replaceInput.value;
// Transfer dialog contents to the find service.
saveFindData();
var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"].createInstance().QueryInterface(Components.interfaces.nsIFind);
finder.caseSensitive = gReplaceDialog.caseSensitive.checked;
finder.findBackwards = gReplaceDialog.searchBackwards.checked;
// We want the whole operation to be undoable in one swell foop,
// so start a transaction:
gEditor.beginTransaction();
// and to make sure we close the transaction, guard against exceptions:
try {
// Make a range containing the current selection,
// so we don't go past it when we wrap.
var selection = gEditor.selection;
var selecRange;
if (selection.rangeCount > 0)
selecRange = selection.getRangeAt(0);
var origRange = selecRange.cloneRange();
// We'll need a range for the whole document:
var wholeDocRange = gEditor.document.createRange();
var rootNode = gEditor.rootElement.QueryInterface(Components.interfaces.nsIDOMNode);
wholeDocRange.selectNodeContents(rootNode);
// And start and end points:
var endPt = gEditor.document.createRange();
if (gReplaceDialog.searchBackwards.checked)
{
endPt.setStart(wholeDocRange.startContainer, wholeDocRange.startOffset);
endPt.setEnd(wholeDocRange.startContainer, wholeDocRange.startOffset);
}
else
{
endPt.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset);
endPt.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset);
}
// Find and replace from here to end (start) of document:
var foundRange;
var searchRange = wholeDocRange.cloneRange();
while ((foundRange = finder.Find(findStr, searchRange,
selecRange, endPt)) != null)
{
gEditor.selection.removeAllRanges();
gEditor.selection.addRange(foundRange);
// The editor will leave the caret at the end of the replaced text.
// For reverse finds, we need it at the beginning,
// so save the next position now.
if (gReplaceDialog.searchBackwards.checked)
{
selecRange = foundRange.cloneRange();
selecRange.setEnd(selecRange.startContainer, selecRange.startOffset);
}
// nsPlaintextEditor::InsertText fails if the string is empty,
// so make that a special case:
if (repStr == "")
gEditor.deleteSelection(0);
else
gEditor.insertText(repStr);
// If we're going forward, we didn't save selecRange before, so do it now:
if (!gReplaceDialog.searchBackwards.checked)
{
selection = gEditor.selection;
if (selection.rangeCount <= 0) {
gEditor.endTransaction();
return;
}
selecRange = selection.getRangeAt(0).cloneRange();
}
}
// If no wrapping, then we're done
if (!gReplaceDialog.wrap.checked) {
gEditor.endTransaction();
return;
}
// If wrapping, find from start/end of document back to start point.
if (gReplaceDialog.searchBackwards.checked)
{
// Collapse origRange to end
origRange.setStart(origRange.endContainer, origRange.endOffset);
// Set current position to document end
selecRange.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset);
selecRange.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset);
}
else
{
// Collapse origRange to start
origRange.setEnd(origRange.startContainer, origRange.startOffset);
// Set current position to document start
selecRange.setStart(wholeDocRange.startContainer,
wholeDocRange.startOffset);
selecRange.setEnd(wholeDocRange.startContainer, wholeDocRange.startOffset);
}
while ((foundRange = finder.Find(findStr, wholeDocRange,
selecRange, origRange)) != null)
{
gEditor.selection.removeAllRanges();
gEditor.selection.addRange(foundRange);
// Save insert point for backward case
if (gReplaceDialog.searchBackwards.checked)
{
selecRange = foundRange.cloneRange();
selecRange.setEnd(selecRange.startContainer, selecRange.startOffset);
}
// nsPlaintextEditor::InsertText fails if the string is empty,
// so make that a special case:
if (repStr == "")
gEditor.deleteSelection(0);
else
gEditor.insertText(repStr);
// Get insert point for forward case
if (!gReplaceDialog.searchBackwards.checked)
{
selection = gEditor.selection;
if (selection.rangeCount <= 0) {
gEditor.endTransaction();
return;
}
selecRange = selection.getRangeAt(0);
}
}
} // end try
catch (e) { }
gEditor.endTransaction();
}
function doEnabling()
{
var findStr = gReplaceDialog.findInput.value;
var repStr = gReplaceDialog.replaceInput.value;
gReplaceDialog.enabled = findStr;
gReplaceDialog.findNext.disabled = !findStr;
gReplaceDialog.replace.disabled = !findStr;
gReplaceDialog.replaceAndFind.disabled = !findStr;
gReplaceDialog.replaceAll.disabled = !findStr;
}