mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 12:15:51 +00:00
416 lines
14 KiB
JavaScript
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;
|
|
}
|