blob: f1070ace33c5917854dc081509a235cb1eb42510 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* SAP SE, christian.georgi@sap.com - Bug 487357: Make find dialog content scrollable
* Pierre-Yves B., pyvesdev@gmail.com - Bug 121634: [find/replace] status bar must show the string being searched when "String Not Found"
*******************************************************************************/
package org.eclipse.ui.texteditor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.PatternSyntaxException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.jface.action.LegacyActionTools;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.fieldassist.ComboContentAdapter;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.util.Util;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.FindReplaceDocumentAdapterContentProposalProvider;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.text.IFindReplaceTargetExtension;
import org.eclipse.jface.text.IFindReplaceTargetExtension3;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter;
import org.eclipse.ui.internal.texteditor.NLSUtility;
import org.eclipse.ui.internal.texteditor.SWTUtil;
import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
/**
* Find/Replace dialog. The dialog is opened on a particular
* target but can be re-targeted. Internally used by the <code>FindReplaceAction</code>
*/
class FindReplaceDialog extends Dialog {
/**
* Updates the find replace dialog on activation changes.
*/
class ActivationListener extends ShellAdapter {
@Override
public void shellActivated(ShellEvent e) {
fActiveShell= (Shell)e.widget;
updateButtonState();
if (fGiveFocusToFindField && getShell() == fActiveShell && okToUse(fFindField))
fFindField.setFocus();
}
@Override
public void shellDeactivated(ShellEvent e) {
fGiveFocusToFindField= false;
storeSettings();
fGlobalRadioButton.setSelection(true);
fSelectedRangeRadioButton.setSelection(false);
fUseSelectedLines= false;
if (fTarget != null && (fTarget instanceof IFindReplaceTargetExtension))
((IFindReplaceTargetExtension) fTarget).setScope(null);
fOldScope= null;
fActiveShell= null;
updateButtonState();
}
}
/**
* Modify listener to update the search result in case of incremental search.
* @since 2.0
*/
private class FindModifyListener implements ModifyListener {
// XXX: Workaround for Combo bug on Linux (see bug 404202 and bug 410603)
private boolean fIgnoreNextEvent;
private void ignoreNextEvent() {
fIgnoreNextEvent= true;
}
@Override
public void modifyText(ModifyEvent e) {
// XXX: Workaround for Combo bug on Linux (see bug 404202 and bug 410603)
if (fIgnoreNextEvent) {
fIgnoreNextEvent= false;
return;
}
if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) {
if (fFindField.getText().equals("") && fTarget != null) { //$NON-NLS-1$
// empty selection at base location
int offset= fIncrementalBaseLocation.x;
if (isForwardSearch() && !fNeedsInitialFindBeforeReplace || !isForwardSearch() && fNeedsInitialFindBeforeReplace)
offset= offset + fIncrementalBaseLocation.y;
fNeedsInitialFindBeforeReplace= false;
findAndSelect(offset, "", isForwardSearch(), isCaseSensitiveSearch(), isWholeWordSearch(), isRegExSearchAvailableAndChecked()); //$NON-NLS-1$
} else {
performSearch(false, false, isForwardSearch());
}
}
updateButtonState(!isIncrementalSearch());
}
}
/** The size of the dialogs search history. */
private static final int HISTORY_SIZE= 15;
private Point fIncrementalBaseLocation;
private boolean fWrapInit, fCaseInit, fWholeWordInit, fForwardInit, fGlobalInit, fIncrementalInit;
/**
* Tells whether an initial find operation is needed
* before the replace operation.
* @since 3.0
*/
private boolean fNeedsInitialFindBeforeReplace;
/**
* Initial value for telling whether the search string is a regular expression.
* @since 3.0
*/
boolean fIsRegExInit;
private List<String> fFindHistory;
private List<String> fReplaceHistory;
private IRegion fOldScope;
private boolean fIsTargetEditable;
private IFindReplaceTarget fTarget;
private Shell fParentShell;
private Shell fActiveShell;
private final ActivationListener fActivationListener= new ActivationListener();
private final FindModifyListener fFindModifyListener= new FindModifyListener();
private Label fReplaceLabel, fStatusLabel;
private Button fForwardRadioButton, fGlobalRadioButton, fSelectedRangeRadioButton;
private Button fCaseCheckBox, fWrapCheckBox, fWholeWordCheckBox, fIncrementalCheckBox;
/**
* Checkbox for selecting whether the search string is a regular expression.
* @since 3.0
*/
private Button fIsRegExCheckBox;
private Button fReplaceSelectionButton, fReplaceFindButton, fFindNextButton, fReplaceAllButton;
private Combo fFindField, fReplaceField;
/**
* Find and replace command adapters.
* @since 3.3
*/
private ContentAssistCommandAdapter fContentAssistFindField, fContentAssistReplaceField;
private Rectangle fDialogPositionInit;
private IDialogSettings fDialogSettings;
/**
* Tells whether the target supports regular expressions.
* <code>true</code> if the target supports regular expressions
* @since 3.0
*/
private boolean fIsTargetSupportingRegEx;
/**
* Tells whether fUseSelectedLines radio is checked.
* @since 3.0
*/
private boolean fUseSelectedLines;
/**
* <code>true</code> if the find field should receive focus the next time
* the dialog is activated, <code>false</code> otherwise.
* @since 3.0
*/
private boolean fGiveFocusToFindField= true;
/**
* Holds the mnemonic/button pairs for all buttons.
* @since 3.7
*/
private HashMap<Character, Button> fMnemonicButtonMap= new HashMap<>();
/**
* Creates a new dialog with the given shell as parent.
* @param parentShell the parent shell
*/
public FindReplaceDialog(Shell parentShell) {
super(parentShell);
fParentShell= null;
fTarget= null;
fDialogPositionInit= null;
fFindHistory= new ArrayList<>(HISTORY_SIZE);
fReplaceHistory= new ArrayList<>(HISTORY_SIZE);
fWrapInit= false;
fCaseInit= false;
fIsRegExInit= false;
fWholeWordInit= false;
fIncrementalInit= false;
fGlobalInit= true;
fForwardInit= true;
readConfiguration();
setShellStyle(getShellStyle() ^ SWT.APPLICATION_MODAL | SWT.MODELESS);
setBlockOnOpen(false);
}
@Override
protected boolean isResizable() {
return true;
}
/**
* Returns this dialog's parent shell.
* @return the dialog's parent shell
*/
@Override
public Shell getParentShell() {
return super.getParentShell();
}
/**
* Returns <code>true</code> if control can be used.
*
* @param control the control to be checked
* @return <code>true</code> if control can be used
*/
private boolean okToUse(Control control) {
return control != null && !control.isDisposed();
}
@Override
public void create() {
super.create();
Shell shell= getShell();
shell.addShellListener(fActivationListener);
// set help context
PlatformUI.getWorkbench().getHelpSystem().setHelp(shell, IAbstractTextEditorHelpContextIds.FIND_REPLACE_DIALOG);
// fill in combo contents
fFindField.removeModifyListener(fFindModifyListener);
updateCombo(fFindField, fFindHistory);
fFindField.addModifyListener(fFindModifyListener);
updateCombo(fReplaceField, fReplaceHistory);
// get find string
initFindStringFromSelection();
// set dialog position
if (fDialogPositionInit != null)
shell.setBounds(fDialogPositionInit);
shell.setText(EditorMessages.FindReplace_title);
// shell.setImage(null);
}
/**
* Create the button section of the find/replace dialog.
*
* @param parent the parent composite
* @return the button section
*/
private Composite createButtonSection(Composite parent) {
Composite panel= new Composite(parent, SWT.NONE);
GridLayout layout= new GridLayout();
layout.numColumns= -2; // this is intended
panel.setLayout(layout);
fFindNextButton= makeButton(panel, EditorMessages.FindReplace_FindNextButton_label, 102, true, new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked())
initIncrementalBaseLocation();
fNeedsInitialFindBeforeReplace= false;
performSearch(((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) ^ isForwardSearch());
updateFindHistory();
}
});
setGridData(fFindNextButton, SWT.FILL, true, SWT.FILL, false);
fReplaceFindButton= makeButton(panel, EditorMessages.FindReplace_ReplaceFindButton_label, 103, false, new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (fNeedsInitialFindBeforeReplace)
performSearch(((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) ^ isForwardSearch());
if (performReplaceSelection())
performSearch(((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) ^ isForwardSearch());
updateFindAndReplaceHistory();
}
});
setGridData(fReplaceFindButton, SWT.FILL, false, SWT.FILL, false);
fReplaceSelectionButton= makeButton(panel, EditorMessages.FindReplace_ReplaceSelectionButton_label, 104, false, new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (fNeedsInitialFindBeforeReplace)
performSearch();
performReplaceSelection();
updateButtonState();
updateFindAndReplaceHistory();
}
});
setGridData(fReplaceSelectionButton, SWT.FILL, false, SWT.FILL, false);
fReplaceAllButton= makeButton(panel, EditorMessages.FindReplace_ReplaceAllButton_label, 105, false, new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
performReplaceAll();
updateFindAndReplaceHistory();
}
});
setGridData(fReplaceAllButton, SWT.FILL, true, SWT.FILL, false);
// Make the all the buttons the same size as the Remove Selection button.
fReplaceAllButton.setEnabled(isEditable());
return panel;
}
/**
* Creates the options configuration section of the find replace dialog.
*
* @param parent the parent composite
* @return the options configuration section
*/
private Composite createConfigPanel(Composite parent) {
Composite panel= new Composite(parent, SWT.NONE);
GridLayout layout= new GridLayout();
layout.numColumns= 2;
layout.makeColumnsEqualWidth= true;
panel.setLayout(layout);
Composite directionGroup= createDirectionGroup(panel);
setGridData(directionGroup, SWT.FILL, true, SWT.FILL, false);
Composite scopeGroup= createScopeGroup(panel);
setGridData(scopeGroup, SWT.FILL, true, SWT.FILL, false);
Composite optionsGroup= createOptionsGroup(panel);
setGridData(optionsGroup, SWT.FILL, true, SWT.FILL, true);
((GridData)optionsGroup.getLayoutData()).horizontalSpan= 2;
return panel;
}
@Override
protected Control createContents(Composite parent) {
Composite panel= new Composite(parent, SWT.NULL);
GridLayout layout= new GridLayout();
layout.numColumns= 1;
layout.makeColumnsEqualWidth= true;
panel.setLayout(layout);
setGridData(panel, SWT.FILL, true, SWT.FILL, true);
ScrolledComposite scrolled= new ScrolledComposite(panel, SWT.V_SCROLL);
setGridData(scrolled, SWT.FILL, true, SWT.FILL, true);
Composite mainArea = new Composite(scrolled, SWT.NONE);
setGridData(mainArea, SWT.FILL, true, SWT.FILL, true);
mainArea.setLayout(new GridLayout(1, true));
Composite inputPanel= createInputPanel(mainArea);
setGridData(inputPanel, SWT.FILL, true, SWT.TOP, false);
Composite configPanel= createConfigPanel(mainArea);
setGridData(configPanel, SWT.FILL, true, SWT.TOP, true);
scrolled.setContent(mainArea);
scrolled.setExpandHorizontal(true);
scrolled.setExpandVertical(true);
scrolled.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
scrolled.setMinHeight(mainArea.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
}
});
Composite buttonPanelB= createButtonSection(panel);
setGridData(buttonPanelB, SWT.RIGHT, true, SWT.BOTTOM, false);
Composite statusBar= createStatusAndCloseButton(panel);
setGridData(statusBar, SWT.FILL, true, SWT.BOTTOM, false);
panel.addTraverseListener(new TraverseListener() {
@Override
public void keyTraversed(TraverseEvent e) {
if (e.detail == SWT.TRAVERSE_RETURN) {
if (!Util.isMac()) {
Control controlWithFocus= getShell().getDisplay().getFocusControl();
if (controlWithFocus != null && (controlWithFocus.getStyle() & SWT.PUSH) == SWT.PUSH)
return;
}
Event event= new Event();
event.type= SWT.Selection;
event.stateMask= e.stateMask;
fFindNextButton.notifyListeners(SWT.Selection, event);
e.doit= false;
}
else if (e.detail == SWT.TRAVERSE_MNEMONIC) {
Character mnemonic= Character.valueOf(Character.toLowerCase(e.character));
if (fMnemonicButtonMap.containsKey(mnemonic)) {
Button button= fMnemonicButtonMap.get(mnemonic);
if ((fFindField.isFocusControl() || fReplaceField.isFocusControl() || (button.getStyle() & SWT.PUSH) != 0)
&& button.isEnabled()) {
Event event= new Event();
event.type= SWT.Selection;
event.stateMask= e.stateMask;
if ((button.getStyle() & SWT.RADIO) != 0) {
Composite buttonParent= button.getParent();
if (buttonParent != null) {
Control[] children= buttonParent.getChildren();
for (int i= 0; i < children.length; i++)
((Button)children[i]).setSelection(false);
}
button.setSelection(true);
} else {
button.setSelection(!button.getSelection());
}
button.notifyListeners(SWT.Selection, event);
e.detail= SWT.TRAVERSE_NONE;
e.doit= true;
}
}
}
}
});
updateButtonState();
applyDialogFont(panel);
return panel;
}
private void setContentAssistsEnablement(boolean enable) {
fContentAssistFindField.setEnabled(enable);
fContentAssistReplaceField.setEnabled(enable);
}
/**
* Creates the direction defining part of the options defining section
* of the find replace dialog.
*
* @param parent the parent composite
* @return the direction defining part
*/
private Composite createDirectionGroup(Composite parent) {
Composite panel= new Composite(parent, SWT.NONE);
GridLayout layout= new GridLayout();
layout.marginWidth= 0;
layout.marginHeight= 0;
panel.setLayout(layout);
Group group= new Group(panel, SWT.SHADOW_ETCHED_IN);
group.setText(EditorMessages.FindReplace_Direction);
GridLayout groupLayout= new GridLayout();
group.setLayout(groupLayout);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
SelectionListener selectionListener= new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked())
initIncrementalBaseLocation();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
};
fForwardRadioButton= new Button(group, SWT.RADIO | SWT.LEFT);
fForwardRadioButton.setText(EditorMessages.FindReplace_ForwardRadioButton_label);
setGridData(fForwardRadioButton, SWT.LEFT, false, SWT.CENTER, false);
fForwardRadioButton.addSelectionListener(selectionListener);
storeButtonWithMnemonicInMap(fForwardRadioButton);
Button backwardRadioButton= new Button(group, SWT.RADIO | SWT.LEFT);
backwardRadioButton.setText(EditorMessages.FindReplace_BackwardRadioButton_label);
setGridData(backwardRadioButton, SWT.LEFT, false, SWT.CENTER, false);
backwardRadioButton.addSelectionListener(selectionListener);
storeButtonWithMnemonicInMap(backwardRadioButton);
backwardRadioButton.setSelection(!fForwardInit);
fForwardRadioButton.setSelection(fForwardInit);
return panel;
}
/**
* Creates the scope defining part of the find replace dialog.
*
* @param parent the parent composite
* @return the scope defining part
* @since 2.0
*/
private Composite createScopeGroup(Composite parent) {
Composite panel= new Composite(parent, SWT.NONE);
GridLayout layout= new GridLayout();
layout.marginWidth= 0;
layout.marginHeight= 0;
panel.setLayout(layout);
Group group= new Group(panel, SWT.SHADOW_ETCHED_IN);
group.setText(EditorMessages.FindReplace_Scope);
GridLayout groupLayout= new GridLayout();
group.setLayout(groupLayout);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
fGlobalRadioButton= new Button(group, SWT.RADIO | SWT.LEFT);
fGlobalRadioButton.setText(EditorMessages.FindReplace_GlobalRadioButton_label);
setGridData(fGlobalRadioButton, SWT.LEFT, false, SWT.CENTER, false);
fGlobalRadioButton.setSelection(fGlobalInit);
fGlobalRadioButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
if (!fGlobalRadioButton.getSelection() || !fUseSelectedLines)
return;
fUseSelectedLines= false;
useSelectedLines(false);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
storeButtonWithMnemonicInMap(fGlobalRadioButton);
fSelectedRangeRadioButton= new Button(group, SWT.RADIO | SWT.LEFT);
fSelectedRangeRadioButton.setText(EditorMessages.FindReplace_SelectedRangeRadioButton_label);
setGridData(fSelectedRangeRadioButton, SWT.LEFT, false, SWT.CENTER, false);
fSelectedRangeRadioButton.setSelection(!fGlobalInit);
fUseSelectedLines= !fGlobalInit;
fSelectedRangeRadioButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
if (!fSelectedRangeRadioButton.getSelection() || fUseSelectedLines)
return;
fUseSelectedLines= true;
useSelectedLines(true);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
storeButtonWithMnemonicInMap(fSelectedRangeRadioButton);
return panel;
}
/**
* Tells the dialog to perform searches only in the scope given by the actually selected lines.
* @param selectedLines <code>true</code> if selected lines should be used
* @since 2.0
*/
private void useSelectedLines(boolean selectedLines) {
if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked())
initIncrementalBaseLocation();
if (fTarget == null || !(fTarget instanceof IFindReplaceTargetExtension))
return;
IFindReplaceTargetExtension extensionTarget= (IFindReplaceTargetExtension) fTarget;
if (selectedLines) {
IRegion scope;
if (fOldScope == null) {
Point lineSelection= extensionTarget.getLineSelection();
scope= new Region(lineSelection.x, lineSelection.y);
} else {
scope= fOldScope;
fOldScope= null;
}
int offset= isForwardSearch()
? scope.getOffset()
: scope.getOffset() + scope.getLength();
extensionTarget.setSelection(offset, 0);
extensionTarget.setScope(scope);
} else {
fOldScope= extensionTarget.getScope();
extensionTarget.setScope(null);
}
}
/**
* Creates the panel where the user specifies the text to search
* for and the optional replacement text.
*
* @param parent the parent composite
* @return the input panel
*/
private Composite createInputPanel(Composite parent) {
ModifyListener listener= new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
updateButtonState();
}
};
Composite panel= new Composite(parent, SWT.NULL);
GridLayout layout= new GridLayout();
layout.numColumns= 2;
panel.setLayout(layout);
Label findLabel= new Label(panel, SWT.LEFT);
findLabel.setText(EditorMessages.FindReplace_Find_label);
setGridData(findLabel, SWT.LEFT, false, SWT.CENTER, false);
// Create the find content assist field
ComboContentAdapter contentAdapter= new ComboContentAdapter();
FindReplaceDocumentAdapterContentProposalProvider findProposer= new FindReplaceDocumentAdapterContentProposalProvider(true);
fFindField= new Combo(panel, SWT.DROP_DOWN | SWT.BORDER);
fContentAssistFindField= new ContentAssistCommandAdapter(
fFindField,
contentAdapter,
findProposer,
ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS,
new char[0],
true);
setGridData(fFindField, SWT.FILL, true, SWT.CENTER, false);
addDecorationMargin(fFindField);
fFindField.addModifyListener(fFindModifyListener);
fReplaceLabel= new Label(panel, SWT.LEFT);
fReplaceLabel.setText(EditorMessages.FindReplace_Replace_label);
setGridData(fReplaceLabel, SWT.LEFT, false, SWT.CENTER, false);
// Create the replace content assist field
FindReplaceDocumentAdapterContentProposalProvider replaceProposer= new FindReplaceDocumentAdapterContentProposalProvider(false);
fReplaceField= new Combo(panel, SWT.DROP_DOWN | SWT.BORDER);
fContentAssistReplaceField= new ContentAssistCommandAdapter(
fReplaceField,
contentAdapter, replaceProposer,
ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS,
new char[0],
true);
setGridData(fReplaceField, SWT.FILL, true, SWT.CENTER, false);
addDecorationMargin(fReplaceField);
fReplaceField.addModifyListener(listener);
return panel;
}
/**
* Creates the functional options part of the options defining
* section of the find replace dialog.
*
* @param parent the parent composite
* @return the options group
*/
private Composite createOptionsGroup(Composite parent) {
Composite panel= new Composite(parent, SWT.NONE);
GridLayout layout= new GridLayout();
layout.marginWidth= 0;
layout.marginHeight= 0;
panel.setLayout(layout);
Group group= new Group(panel, SWT.SHADOW_NONE);
group.setText(EditorMessages.FindReplace_Options);
GridLayout groupLayout= new GridLayout();
groupLayout.numColumns= 2;
groupLayout.makeColumnsEqualWidth= true;
group.setLayout(groupLayout);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
SelectionListener selectionListener= new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
storeSettings();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
};
fCaseCheckBox= new Button(group, SWT.CHECK | SWT.LEFT);
fCaseCheckBox.setText(EditorMessages.FindReplace_CaseCheckBox_label);
setGridData(fCaseCheckBox, SWT.LEFT, false, SWT.CENTER, false);
fCaseCheckBox.setSelection(fCaseInit);
fCaseCheckBox.addSelectionListener(selectionListener);
storeButtonWithMnemonicInMap(fCaseCheckBox);
fWrapCheckBox= new Button(group, SWT.CHECK | SWT.LEFT);
fWrapCheckBox.setText(EditorMessages.FindReplace_WrapCheckBox_label);
setGridData(fWrapCheckBox, SWT.LEFT, false, SWT.CENTER, false);
fWrapCheckBox.setSelection(fWrapInit);
fWrapCheckBox.addSelectionListener(selectionListener);
storeButtonWithMnemonicInMap(fWrapCheckBox);
fWholeWordCheckBox= new Button(group, SWT.CHECK | SWT.LEFT);
fWholeWordCheckBox.setText(EditorMessages.FindReplace_WholeWordCheckBox_label);
setGridData(fWholeWordCheckBox, SWT.LEFT, false, SWT.CENTER, false);
fWholeWordCheckBox.setSelection(fWholeWordInit);
fWholeWordCheckBox.addSelectionListener(selectionListener);
storeButtonWithMnemonicInMap(fWholeWordCheckBox);
fIncrementalCheckBox= new Button(group, SWT.CHECK | SWT.LEFT);
fIncrementalCheckBox.setText(EditorMessages.FindReplace_IncrementalCheckBox_label);
setGridData(fIncrementalCheckBox, SWT.LEFT, false, SWT.CENTER, false);
fIncrementalCheckBox.setSelection(fIncrementalInit);
fIncrementalCheckBox.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
if (isIncrementalSearch() && !isRegExSearch())
initIncrementalBaseLocation();
storeSettings();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
storeButtonWithMnemonicInMap(fIncrementalCheckBox);
fIsRegExCheckBox= new Button(group, SWT.CHECK | SWT.LEFT);
fIsRegExCheckBox.setText(EditorMessages.FindReplace_RegExCheckbox_label);
setGridData(fIsRegExCheckBox, SWT.LEFT, false, SWT.CENTER, false);
((GridData)fIsRegExCheckBox.getLayoutData()).horizontalSpan= 2;
fIsRegExCheckBox.setSelection(fIsRegExInit);
fIsRegExCheckBox.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
boolean newState= fIsRegExCheckBox.getSelection();
fIncrementalCheckBox.setEnabled(!newState);
updateButtonState();
storeSettings();
setContentAssistsEnablement(newState);
}
});
storeButtonWithMnemonicInMap(fIsRegExCheckBox);
fWholeWordCheckBox.setEnabled(!isRegExSearchAvailableAndChecked());
fWholeWordCheckBox.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateButtonState();
}
});
fIncrementalCheckBox.setEnabled(!isRegExSearchAvailableAndChecked());
return panel;
}
/**
* Creates the status and close section of the dialog.
*
* @param parent the parent composite
* @return the status and close button
*/
private Composite createStatusAndCloseButton(Composite parent) {
Composite panel= new Composite(parent, SWT.NULL);
GridLayout layout= new GridLayout();
layout.numColumns= 2;
layout.marginWidth= 0;
layout.marginHeight= 0;
panel.setLayout(layout);
fStatusLabel= new Label(panel, SWT.LEFT);
setGridData(fStatusLabel, SWT.FILL, true, SWT.CENTER, false);
String label= EditorMessages.FindReplace_CloseButton_label;
Button closeButton= createButton(panel, 101, label, false);
setGridData(closeButton, SWT.RIGHT, false, SWT.BOTTOM, false);
return panel;
}
/*
* @see Dialog#buttonPressed
*/
@Override
protected void buttonPressed(int buttonID) {
if (buttonID == 101)
close();
}
// ------- action invocation ---------------------------------------
/**
* Returns the position of the specified search string, or <code>-1</code> if the string can not
* be found when searching using the given options.
*
* @param findString the string to search for
* @param startPosition the position at which to start the search
* @param forwardSearch the direction of the search
* @param caseSensitive should the search be case sensitive
* @param wrapSearch should the search wrap to the start/end if arrived at the end/start
* @param wholeWord does the search string represent a complete word
* @param regExSearch if <code>true</code> findString represents a regular expression
* @param beep if <code>true</code> beeps when search does not find a match or needs to wrap
* @return the occurrence of the find string following the options or <code>-1</code> if nothing
* found
* @since 3.0
*/
private int findIndex(String findString, int startPosition, boolean forwardSearch, boolean caseSensitive, boolean wrapSearch, boolean wholeWord, boolean regExSearch, boolean beep) {
if (forwardSearch) {
int index= findAndSelect(startPosition, findString, true, caseSensitive, wholeWord, regExSearch);
if (index == -1) {
if (beep && okToUse(getShell()))
getShell().getDisplay().beep();
if (wrapSearch) {
statusMessage(EditorMessages.FindReplace_Status_wrapped_label);
index= findAndSelect(-1, findString, true, caseSensitive, wholeWord, regExSearch);
}
}
return index;
}
// backward
int index= startPosition == 0 ? -1 : findAndSelect(startPosition - 1, findString, false, caseSensitive, wholeWord, regExSearch);
if (index == -1) {
if (beep && okToUse(getShell()))
getShell().getDisplay().beep();
if (wrapSearch) {
statusMessage(EditorMessages.FindReplace_Status_wrapped_label);
index= findAndSelect(-1, findString, false, caseSensitive, wholeWord, regExSearch);
}
}
return index;
}
/**
* Searches for a string starting at the given offset and using the specified search
* directives. If a string has been found it is selected and its start offset is
* returned.
*
* @param offset the offset at which searching starts
* @param findString the string which should be found
* @param forwardSearch the direction of the search
* @param caseSensitive <code>true</code> performs a case sensitive search, <code>false</code> an insensitive search
* @param wholeWord if <code>true</code> only occurrences are reported in which the findString stands as a word by itself
* @param regExSearch if <code>true</code> findString represents a regular expression
* @return the position of the specified string, or -1 if the string has not been found
* @since 3.0
*/
private int findAndSelect(int offset, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, boolean regExSearch) {
if (fTarget instanceof IFindReplaceTargetExtension3)
return ((IFindReplaceTargetExtension3)fTarget).findAndSelect(offset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
return fTarget.findAndSelect(offset, findString, forwardSearch, caseSensitive, wholeWord);
}
/**
* Replaces the selection with <code>replaceString</code>. If
* <code>regExReplace</code> is <code>true</code>,
* <code>replaceString</code> is a regex replace pattern which will get
* expanded if the underlying target supports it. Returns the region of the
* inserted text; note that the returned selection covers the expanded
* pattern in case of regex replace.
*
* @param replaceString the replace string (or a regex pattern)
* @param regExReplace <code>true</code> if <code>replaceString</code>
* is a pattern
* @return the selection after replacing, i.e. the inserted text
* @since 3.0
*/
Point replaceSelection(String replaceString, boolean regExReplace) {
if (fTarget instanceof IFindReplaceTargetExtension3)
((IFindReplaceTargetExtension3)fTarget).replaceSelection(replaceString, regExReplace);
else
fTarget.replaceSelection(replaceString);
return fTarget.getSelection();
}
/**
* Returns whether the specified search string can be found using the given options.
*
* @param findString the string to search for
* @param forwardSearch the direction of the search
* @param caseSensitive should the search be case sensitive
* @param wrapSearch should the search wrap to the start/end if arrived at the end/start
* @param wholeWord does the search string represent a complete word
* @param incremental is this an incremental search
* @param regExSearch if <code>true</code> findString represents a regular expression
* @param beep if <code>true</code> beeps when search does not find a match or needs to wrap
* @return <code>true</code> if the search string can be found using the given options
*
* @since 3.0
*/
private boolean findNext(String findString, boolean forwardSearch, boolean caseSensitive, boolean wrapSearch, boolean wholeWord, boolean incremental, boolean regExSearch, boolean beep) {
if (fTarget == null)
return false;
Point r= null;
if (incremental)
r= fIncrementalBaseLocation;
else
r= fTarget.getSelection();
int findReplacePosition= r.x;
if (forwardSearch && !fNeedsInitialFindBeforeReplace || !forwardSearch && fNeedsInitialFindBeforeReplace)
findReplacePosition += r.y;
fNeedsInitialFindBeforeReplace= false;
int index= findIndex(findString, findReplacePosition, forwardSearch, caseSensitive, wrapSearch, wholeWord, regExSearch, beep);
if (index == -1) {
String msg= NLSUtility.format(EditorMessages.FindReplace_Status_noMatchWithValue_label, findString);
statusMessage(false, EditorMessages.FindReplace_Status_noMatch_label, msg);
return false;
}
if (forwardSearch && index >= findReplacePosition || !forwardSearch && index <= findReplacePosition)
statusMessage(""); //$NON-NLS-1$
return true;
}
/**
* Returns the dialog's boundaries.
* @return the dialog's boundaries
*/
private Rectangle getDialogBoundaries() {
if (okToUse(getShell()))
return getShell().getBounds();
return fDialogPositionInit;
}
/**
* Returns the dialog's history.
* @return the dialog's history
*/
private List<String> getFindHistory() {
return fFindHistory;
}
// ------- accessors ---------------------------------------
/**
* Retrieves the string to search for from the appropriate text input field and returns it.
* @return the search string
*/
private String getFindString() {
if (okToUse(fFindField)) {
return fFindField.getText();
}
return ""; //$NON-NLS-1$
}
/**
* Returns the dialog's replace history.
* @return the dialog's replace history
*/
private List<String> getReplaceHistory() {
return fReplaceHistory;
}
/**
* Retrieves the replacement string from the appropriate text input field and returns it.
* @return the replacement string
*/
private String getReplaceString() {
if (okToUse(fReplaceField)) {
return fReplaceField.getText();
}
return ""; //$NON-NLS-1$
}
// ------- init / close ---------------------------------------
/**
* Returns the first line of the given selection.
*
* @param selection the selection
* @return the first line of the selection
*/
private String getFirstLine(String selection) {
if (!selection.isEmpty()) {
int[] info= TextUtilities.indexOf(TextUtilities.DELIMITERS, selection, 0);
if (info[0] > 0)
return selection.substring(0, info[0]);
else if (info[0] == -1)
return selection;
}
return ""; //$NON-NLS-1$
}
/**
* @see org.eclipse.jface.window.Window#close()
*/
@Override
public boolean close() {
handleDialogClose();
return super.close();
}
/**
* Removes focus changed listener from browser and stores settings for re-open.
*/
private void handleDialogClose() {
// remove listeners
if (okToUse(fFindField)) {
fFindField.removeModifyListener(fFindModifyListener);
}
if (fParentShell != null) {
fParentShell.removeShellListener(fActivationListener);
fParentShell= null;
}
getShell().removeShellListener(fActivationListener);
// store current settings in case of re-open
storeSettings();
if (fTarget != null && fTarget instanceof IFindReplaceTargetExtension)
((IFindReplaceTargetExtension) fTarget).endSession();
// prevent leaks
fActiveShell= null;
fTarget= null;
}
/**
* Writes the current selection to the dialog settings.
* @since 3.0
*/
private void writeSelection() {
if (fTarget == null)
return;
IDialogSettings s= getDialogSettings();
s.put("selection", fTarget.getSelectionText()); //$NON-NLS-1$
}
/**
* Stores the current state in the dialog settings.
* @since 2.0
*/
private void storeSettings() {
fDialogPositionInit= getDialogBoundaries();
fWrapInit= isWrapSearch();
fWholeWordInit= isWholeWordSetting();
fCaseInit= isCaseSensitiveSearch();
fIsRegExInit= isRegExSearch();
fIncrementalInit= isIncrementalSearch();
fForwardInit= isForwardSearch();
writeConfiguration();
}
/**
* Initializes the string to search for and the appropriate
* text in the Find field based on the selection found in the
* action's target.
*/
private void initFindStringFromSelection() {
if (fTarget != null && okToUse(fFindField)) {
String fullSelection= fTarget.getSelectionText();
boolean isRegEx= isRegExSearchAvailableAndChecked();
fFindField.removeModifyListener(fFindModifyListener);
if (!fullSelection.isEmpty()) {
String firstLine= getFirstLine(fullSelection);
String pattern= isRegEx ? FindReplaceDocumentAdapter.escapeForRegExPattern(fullSelection) : firstLine;
fFindField.setText(pattern);
if (!firstLine.equals(fullSelection)) {
// multiple lines selected
useSelectedLines(true);
fGlobalRadioButton.setSelection(false);
fSelectedRangeRadioButton.setSelection(true);
fUseSelectedLines= true;
}
} else {
if ("".equals(fFindField.getText())) { //$NON-NLS-1$
if (!fFindHistory.isEmpty())
fFindField.setText(fFindHistory.get(0));
else
fFindField.setText(""); //$NON-NLS-1$
}
}
fFindField.setSelection(new Point(0, fFindField.getText().length()));
fFindField.addModifyListener(fFindModifyListener);
}
}
/**
* Initializes the anchor used as starting point for incremental searching.
* @since 2.0
*/
private void initIncrementalBaseLocation() {
if (fTarget != null && isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) {
fIncrementalBaseLocation= fTarget.getSelection();
} else {
fIncrementalBaseLocation= new Point(0, 0);
}
}
// ------- history ---------------------------------------
/**
* Retrieves and returns the option case sensitivity from the appropriate check box.
* @return <code>true</code> if case sensitive
*/
private boolean isCaseSensitiveSearch() {
if (okToUse(fCaseCheckBox)) {
return fCaseCheckBox.getSelection();
}
return fCaseInit;
}
/**
* Retrieves and returns the regEx option from the appropriate check box.
*
* @return <code>true</code> if case sensitive
* @since 3.0
*/
private boolean isRegExSearch() {
if (okToUse(fIsRegExCheckBox)) {
return fIsRegExCheckBox.getSelection();
}
return fIsRegExInit;
}
/**
* If the target supports regular expressions search retrieves and returns
* regEx option from appropriate check box.
*
* @return <code>true</code> if regEx is available and checked
* @since 3.0
*/
private boolean isRegExSearchAvailableAndChecked() {
if (okToUse(fIsRegExCheckBox)) {
return fIsTargetSupportingRegEx && fIsRegExCheckBox.getSelection();
}
return fIsRegExInit;
}
/**
* Retrieves and returns the option search direction from the appropriate check box.
* @return <code>true</code> if searching forward
*/
private boolean isForwardSearch() {
if (okToUse(fForwardRadioButton)) {
return fForwardRadioButton.getSelection();
}
return fForwardInit;
}
/**
* Retrieves and returns the option search whole words from the appropriate check box.
* @return <code>true</code> if searching for whole words
*/
private boolean isWholeWordSetting() {
if (okToUse(fWholeWordCheckBox)) {
return fWholeWordCheckBox.getSelection();
}
return fWholeWordInit;
}
/**
* Returns <code>true</code> if searching should be restricted to entire
* words, <code>false</code> if not. This is the case if the respective
* checkbox is turned on, regex is off, and the checkbox is enabled, i.e.
* the current find string is an entire word.
*
* @return <code>true</code> if the search is restricted to whole words
*/
private boolean isWholeWordSearch() {
return isWholeWordSetting() && !isRegExSearchAvailableAndChecked() && (okToUse(fWholeWordCheckBox) ? fWholeWordCheckBox.isEnabled() : true);
}
/**
* Retrieves and returns the option wrap search from the appropriate check box.
* @return <code>true</code> if wrapping while searching
*/
private boolean isWrapSearch() {
if (okToUse(fWrapCheckBox)) {
return fWrapCheckBox.getSelection();
}
return fWrapInit;
}
/**
* Retrieves and returns the option incremental search from the appropriate check box.
* @return <code>true</code> if incremental search
* @since 2.0
*/
private boolean isIncrementalSearch() {
if (okToUse(fIncrementalCheckBox)) {
return fIncrementalCheckBox.getSelection();
}
return fIncrementalInit;
}
/**
* Creates a button.
* @param parent the parent control
* @param label the button label
* @param id the button id
* @param dfltButton is this button the default button
* @param listener a button pressed listener
* @return the new button
*/
private Button makeButton(Composite parent, String label, int id, boolean dfltButton, SelectionListener listener) {
Button button= createButton(parent, id, label, dfltButton);
button.addSelectionListener(listener);
storeButtonWithMnemonicInMap(button);
return button;
}
/**
* Stores the button and its mnemonic in {@link #fMnemonicButtonMap}.
*
* @param button button whose mnemonic has to be stored
* @since 3.7
*/
private void storeButtonWithMnemonicInMap(Button button) {
char mnemonic= LegacyActionTools.extractMnemonic(button.getText());
if (mnemonic != LegacyActionTools.MNEMONIC_NONE)
fMnemonicButtonMap.put(Character.valueOf(Character.toLowerCase(mnemonic)), button);
}
/**
* Returns the status line manager of the active editor or <code>null</code> if there is no such editor.
* @return the status line manager of the active editor
*/
private IEditorStatusLine getStatusLineManager() {
IWorkbenchWindow window= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (window == null)
return null;
IWorkbenchPage page= window.getActivePage();
if (page == null)
return null;
IEditorPart editor= page.getActiveEditor();
if (editor == null)
return null;
return editor.getAdapter(IEditorStatusLine.class);
}
/**
* Sets the given status message in the status line.
*
* @param error <code>true</code> if it is an error
* @param dialogMessage the message to display in the dialog's status line
* @param editorMessage the message to display in the editor's status line
*/
private void statusMessage(boolean error, String dialogMessage, String editorMessage) {
fStatusLabel.setText(dialogMessage);
if (error)
fStatusLabel.setForeground(JFaceColors.getErrorText(fStatusLabel.getDisplay()));
else
fStatusLabel.setForeground(null);
IEditorStatusLine statusLine= getStatusLineManager();
if (statusLine != null)
statusLine.setMessage(error, editorMessage, null);
if (error)
getShell().getDisplay().beep();
}
/**
* Sets the given error message in the status line.
* @param message the message
*/
private void statusError(String message) {
statusMessage(true, message, message);
}
/**
* Sets the given message in the status line.
* @param message the message
*/
private void statusMessage(String message) {
statusMessage(false, message, message);
}
/**
* Replaces all occurrences of the user's findString with
* the replace string. Indicate to the user the number of replacements
* that occur.
*/
private void performReplaceAll() {
int replaceCount= 0;
final String replaceString= getReplaceString();
final String findString= getFindString();
if (findString != null && !findString.isEmpty()) {
class ReplaceAllRunnable implements Runnable {
public int numberOfOccurrences;
@Override
public void run() {
numberOfOccurrences= replaceAll(findString, replaceString == null ? "" : replaceString, isForwardSearch(), isCaseSensitiveSearch(), isWholeWordSearch(), isRegExSearchAvailableAndChecked()); //$NON-NLS-1$
}
}
try {
ReplaceAllRunnable runnable= new ReplaceAllRunnable();
BusyIndicator.showWhile(fActiveShell.getDisplay(), runnable);
replaceCount= runnable.numberOfOccurrences;
if (replaceCount != 0) {
if (replaceCount == 1) { // not plural
statusMessage(EditorMessages.FindReplace_Status_replacement_label);
} else {
String msg= EditorMessages.FindReplace_Status_replacements_label;
msg= NLSUtility.format(msg, String.valueOf(replaceCount));
statusMessage(msg);
}
} else {
String msg= NLSUtility.format(EditorMessages.FindReplace_Status_noMatchWithValue_label, findString);
statusMessage(false, EditorMessages.FindReplace_Status_noMatch_label, msg);
}
} catch (PatternSyntaxException ex) {
statusError(ex.getLocalizedMessage());
} catch (IllegalStateException ex) {
// we don't keep state in this dialog
}
}
writeSelection();
updateButtonState();
}
/**
* Validates the state of the find/replace target.
* @return <code>true</code> if target can be changed, <code>false</code> otherwise
* @since 2.1
*/
private boolean validateTargetState() {
if (fTarget instanceof IFindReplaceTargetExtension2) {
IFindReplaceTargetExtension2 extension= (IFindReplaceTargetExtension2) fTarget;
if (!extension.validateTargetState()) {
statusError(EditorMessages.FindReplaceDialog_read_only);
updateButtonState();
return false;
}
}
return isEditable();
}
/**
* Replaces the current selection of the target with the user's
* replace string.
*
* @return <code>true</code> if the operation was successful
*/
private boolean performReplaceSelection() {
if (!validateTargetState())
return false;
String replaceString= getReplaceString();
if (replaceString == null)
replaceString= ""; //$NON-NLS-1$
boolean replaced;
try {
replaceSelection(replaceString, isRegExSearchAvailableAndChecked());
replaced= true;
writeSelection();
} catch (PatternSyntaxException ex) {
statusError(ex.getLocalizedMessage());
replaced= false;
} catch (IllegalStateException ex) {
replaced= false;
}
return replaced;
}
/**
* Locates the user's findString in the text of the target.
*/
private void performSearch() {
performSearch(isForwardSearch());
}
/**
* Locates the user's findString in the text of the target.
*
* @param forwardSearch <code>true</code> if searching forwards, <code>false</code> otherwise
* @since 3.7
*/
private void performSearch(boolean forwardSearch) {
performSearch(isIncrementalSearch() && !isRegExSearchAvailableAndChecked(), true, forwardSearch);
}
/**
* Locates the user's findString in the text of the target.
*
* @param mustInitIncrementalBaseLocation <code>true</code> if base location must be initialized
* @param beep if <code>true</code> beeps when search does not find a match or needs to wrap
* @param forwardSearch the search direction
* @since 3.0
*/
private void performSearch(boolean mustInitIncrementalBaseLocation, boolean beep, boolean forwardSearch) {
if (mustInitIncrementalBaseLocation)
initIncrementalBaseLocation();
String findString= getFindString();
boolean somethingFound= false;
if (findString != null && !findString.isEmpty()) {
try {
somethingFound= findNext(findString, forwardSearch, isCaseSensitiveSearch(), isWrapSearch(), isWholeWordSearch(), isIncrementalSearch() && !isRegExSearchAvailableAndChecked(), isRegExSearchAvailableAndChecked(), beep);
} catch (PatternSyntaxException ex) {
statusError(ex.getLocalizedMessage());
} catch (IllegalStateException ex) {
// we don't keep state in this dialog
}
}
writeSelection();
updateButtonState(!somethingFound);
}
/**
* Replaces all occurrences of the user's findString with
* the replace string. Returns the number of replacements
* that occur.
*
* @param findString the string to search for
* @param replaceString the replacement string
* @param forwardSearch the search direction
* @param caseSensitive should the search be case sensitive
* @param wholeWord does the search string represent a complete word
* @param regExSearch if <code>true</code> findString represents a regular expression
* @return the number of occurrences
*
* @since 3.0
*/
private int replaceAll(String findString, String replaceString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, boolean regExSearch) {
int replaceCount= 0;
int findReplacePosition= 0;
findReplacePosition= 0;
forwardSearch= true;
if (!validateTargetState())
return replaceCount;
if (fTarget instanceof IFindReplaceTargetExtension)
((IFindReplaceTargetExtension) fTarget).setReplaceAllMode(true);
try {
int index= 0;
while (index != -1) {
index= findAndSelect(findReplacePosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
if (index != -1) { // substring not contained from current position
Point selection= replaceSelection(replaceString, regExSearch);
replaceCount++;
if (forwardSearch)
findReplacePosition= selection.x + selection.y;
else {
findReplacePosition= selection.x - 1;
if (findReplacePosition == -1)
break;
}
}
}
} finally {
if (fTarget instanceof IFindReplaceTargetExtension)
((IFindReplaceTargetExtension) fTarget).setReplaceAllMode(false);
}
return replaceCount;
}
// ------- UI creation ---------------------------------------
/**
* Attaches the given layout specification to the <code>component</code>.
*
* @param component the component
* @param horizontalAlignment horizontal alignment
* @param grabExcessHorizontalSpace grab excess horizontal space
* @param verticalAlignment vertical alignment
* @param grabExcessVerticalSpace grab excess vertical space
*/
private void setGridData(Control component, int horizontalAlignment, boolean grabExcessHorizontalSpace, int verticalAlignment, boolean grabExcessVerticalSpace) {
GridData gd;
if (component instanceof Button && (((Button)component).getStyle() & SWT.PUSH) != 0) {
SWTUtil.setButtonDimensionHint((Button)component);
gd= (GridData)component.getLayoutData();
} else {
gd= new GridData();
component.setLayoutData(gd);
gd.horizontalAlignment= horizontalAlignment;
gd.grabExcessHorizontalSpace= grabExcessHorizontalSpace;
}
gd.verticalAlignment= verticalAlignment;
gd.grabExcessVerticalSpace= grabExcessVerticalSpace;
}
/**
* Adds enough space in the control's layout data margin for the content assist
* decoration.
* @param control the control that needs a margin
* @since 3.3
*/
private void addDecorationMargin(Control control) {
Object layoutData= control.getLayoutData();
if (!(layoutData instanceof GridData))
return;
GridData gd= (GridData)layoutData;
FieldDecoration dec= FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_CONTENT_PROPOSAL);
gd.horizontalIndent= dec.getImage().getBounds().width;
}
/**
* Updates the enabled state of the buttons.
*/
private void updateButtonState() {
updateButtonState(false);
}
/**
* Updates the enabled state of the buttons.
*
* @param disableReplace <code>true</code> if replace button must be disabled
* @since 3.0
*/
private void updateButtonState(boolean disableReplace) {
if (okToUse(getShell()) && okToUse(fFindNextButton)) {
boolean selection= false;
if (fTarget != null)
selection= !fTarget.getSelectionText().isEmpty();
boolean enable= fTarget != null && (fActiveShell == fParentShell || fActiveShell == getShell());
String str= getFindString();
boolean findString= str != null && !str.isEmpty();
fWholeWordCheckBox.setEnabled(isWord(str) && !isRegExSearchAvailableAndChecked());
fFindNextButton.setEnabled(enable && findString);
fReplaceSelectionButton.setEnabled(!disableReplace && enable && isEditable() && selection && (!fNeedsInitialFindBeforeReplace || !isRegExSearchAvailableAndChecked()));
fReplaceFindButton.setEnabled(!disableReplace && enable && isEditable() && findString && selection && (!fNeedsInitialFindBeforeReplace || !isRegExSearchAvailableAndChecked()));
fReplaceAllButton.setEnabled(enable && isEditable() && findString);
}
}
/**
* Tests whether each character in the given string is a letter.
*
* @param str the string to check
* @return <code>true</code> if the given string is a word
* @since 3.0
*/
private boolean isWord(String str) {
if (str == null || str.isEmpty())
return false;
for (int i= 0; i < str.length(); i++) {
if (!Character.isJavaIdentifierPart(str.charAt(i)))
return false;
}
return true;
}
/**
* Updates the given combo with the given content.
* @param combo combo to be updated
* @param content to be put into the combo
*/
private void updateCombo(Combo combo, List<String> content) {
combo.removeAll();
for (int i= 0; i < content.size(); i++) {
combo.add(content.get(i).toString());
}
}
// ------- open / reopen ---------------------------------------
/**
* Called after executed find/replace action to update the history.
*/
private void updateFindAndReplaceHistory() {
updateFindHistory();
if (okToUse(fReplaceField)) {
updateHistory(fReplaceField, fReplaceHistory);
}
}
/**
* Called after executed find action to update the history.
*/
private void updateFindHistory() {
if (okToUse(fFindField)) {
fFindField.removeModifyListener(fFindModifyListener);
// XXX: Workaround for Combo bug on Linux (see bug 404202 and bug 410603)
if (Util.isLinux())
fFindModifyListener.ignoreNextEvent();
updateHistory(fFindField, fFindHistory);
fFindField.addModifyListener(fFindModifyListener);
}
}
/**
* Updates the combo with the history.
* @param combo to be updated
* @param history to be put into the combo
*/
private void updateHistory(Combo combo, List<String> history) {
String findString= combo.getText();
int index= history.indexOf(findString);
if (index != 0) {
if (index != -1) {
history.remove(index);
}
history.add(0, findString);
Point selection= combo.getSelection();
updateCombo(combo, history);
combo.setText(findString);
combo.setSelection(selection);
}
}
/**
* Returns whether the target is editable.
* @return <code>true</code> if target is editable
*/
private boolean isEditable() {
boolean isEditable= (fTarget == null ? false : fTarget.isEditable());
return fIsTargetEditable && isEditable;
}
/**
* Updates this dialog because of a different target.
* @param target the new target
* @param isTargetEditable <code>true</code> if the new target can be modified
* @param initializeFindString <code>true</code> if the find string of this dialog should be initialized based on the viewer's selection
* @since 2.0
*/
public void updateTarget(IFindReplaceTarget target, boolean isTargetEditable, boolean initializeFindString) {
fIsTargetEditable= isTargetEditable;
fNeedsInitialFindBeforeReplace= true;
if (target != fTarget) {
if (fTarget != null && fTarget instanceof IFindReplaceTargetExtension)
((IFindReplaceTargetExtension) fTarget).endSession();
fTarget= target;
if (fTarget != null)
fIsTargetSupportingRegEx= fTarget instanceof IFindReplaceTargetExtension3;
if (fTarget instanceof IFindReplaceTargetExtension) {
((IFindReplaceTargetExtension) fTarget).beginSession();
fGlobalInit= true;
fGlobalRadioButton.setSelection(fGlobalInit);
fSelectedRangeRadioButton.setSelection(!fGlobalInit);
fUseSelectedLines= !fGlobalInit;
}
}
if (okToUse(fIsRegExCheckBox))
fIsRegExCheckBox.setEnabled(fIsTargetSupportingRegEx);
if (okToUse(fWholeWordCheckBox))
fWholeWordCheckBox.setEnabled(!isRegExSearchAvailableAndChecked());
if (okToUse(fIncrementalCheckBox))
fIncrementalCheckBox.setEnabled(!isRegExSearchAvailableAndChecked());
if (okToUse(fReplaceLabel)) {
fReplaceLabel.setEnabled(isEditable());
fReplaceField.setEnabled(isEditable());
if (initializeFindString) {
initFindStringFromSelection();
fGiveFocusToFindField= true;
}
initIncrementalBaseLocation();
updateButtonState();
}
setContentAssistsEnablement(isRegExSearchAvailableAndChecked());
}
/**
* Sets the parent shell of this dialog to be the given shell.
*
* @param shell the new parent shell
*/
@Override
public void setParentShell(Shell shell) {
if (shell != fParentShell) {
if (fParentShell != null)
fParentShell.removeShellListener(fActivationListener);
fParentShell= shell;
fParentShell.addShellListener(fActivationListener);
}
fActiveShell= shell;
}
//--------------- configuration handling --------------
/**
* Returns the dialog settings object used to share state
* between several find/replace dialogs.
*
* @return the dialog settings to be used
*/
private IDialogSettings getDialogSettings() {
IDialogSettings settings= TextEditorPlugin.getDefault().getDialogSettings();
fDialogSettings= settings.getSection(getClass().getName());
if (fDialogSettings == null)
fDialogSettings= settings.addNewSection(getClass().getName());
return fDialogSettings;
}
@Override
protected IDialogSettings getDialogBoundsSettings() {
String sectionName= getClass().getName() + "_dialogBounds"; //$NON-NLS-1$
IDialogSettings settings= TextEditorPlugin.getDefault().getDialogSettings();
IDialogSettings section= settings.getSection(sectionName);
if (section == null)
section= settings.addNewSection(sectionName);
return section;
}
@Override
protected int getDialogBoundsStrategy() {
return DIALOG_PERSISTLOCATION | DIALOG_PERSISTSIZE;
}
/**
* Initializes itself from the dialog settings with the same state
* as at the previous invocation.
*/
private void readConfiguration() {
IDialogSettings s= getDialogSettings();
fWrapInit= s.get("wrap") == null || s.getBoolean("wrap"); //$NON-NLS-1$ //$NON-NLS-2$
fCaseInit= s.getBoolean("casesensitive"); //$NON-NLS-1$
fWholeWordInit= s.getBoolean("wholeword"); //$NON-NLS-1$
fIncrementalInit= s.getBoolean("incremental"); //$NON-NLS-1$
fIsRegExInit= s.getBoolean("isRegEx"); //$NON-NLS-1$
String[] findHistory= s.getArray("findhistory"); //$NON-NLS-1$
if (findHistory != null) {
List<String> history= getFindHistory();
history.clear();
Collections.addAll(history, findHistory);
}
String[] replaceHistory= s.getArray("replacehistory"); //$NON-NLS-1$
if (replaceHistory != null) {
List<String> history= getReplaceHistory();
history.clear();
Collections.addAll(history, replaceHistory);
}
}
/**
* Stores its current configuration in the dialog store.
*/
private void writeConfiguration() {
IDialogSettings s= getDialogSettings();
s.put("wrap", fWrapInit); //$NON-NLS-1$
s.put("casesensitive", fCaseInit); //$NON-NLS-1$
s.put("wholeword", fWholeWordInit); //$NON-NLS-1$
s.put("incremental", fIncrementalInit); //$NON-NLS-1$
s.put("isRegEx", fIsRegExInit); //$NON-NLS-1$
List<String> history= getFindHistory();
String findString= getFindString();
if (!findString.isEmpty())
history.add(0, findString);
writeHistory(history, s, "findhistory"); //$NON-NLS-1$
history= getReplaceHistory();
String replaceString= getReplaceString();
if (!replaceString.isEmpty())
history.add(0, replaceString);
writeHistory(history, s, "replacehistory"); //$NON-NLS-1$
}
/**
* Writes the given history into the given dialog store.
*
* @param history the history
* @param settings the dialog settings
* @param sectionName the section name
* @since 3.2
*/
private void writeHistory(List<String> history, IDialogSettings settings, String sectionName) {
int itemCount= history.size();
Set<String> distinctItems= new HashSet<>(itemCount);
for (int i= 0; i < itemCount; i++) {
String item= history.get(i);
if (distinctItems.contains(item)) {
history.remove(i--);
itemCount--;
} else {
distinctItems.add(item);
}
}
while (history.size() > HISTORY_SIZE)
history.remove(HISTORY_SIZE);
String[] names= new String[history.size()];
history.toArray(names);
settings.put(sectionName, names);
}
}