Skip to content

Commit 45f7e08

Browse files
committed
fix lots of bugs in the find/replace logic to do with multiple matching words in a single node value.
1 parent 29d4ad3 commit 45f7e08

File tree

11 files changed

+326
-137
lines changed

11 files changed

+326
-137
lines changed

src/Application/FormSearch.cs

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,45 +32,27 @@ public FormSearch()
3232
this.buttonReplace.Click += new EventHandler(buttonReplace_Click);
3333
this.buttonReplaceAll.Click += new EventHandler(buttonReplaceAll_Click);
3434
this.comboBoxFind.KeyDown += new KeyEventHandler(comboBoxFind_KeyDown);
35-
this.comboBoxFind.LostFocus += new EventHandler(comboBoxFind_LostFocus);
3635

3736
this.comboBoxFilter.Items.AddRange(new object[] { SearchFilter.Everything, SearchFilter.Names, SearchFilter.Text, SearchFilter.Comments });
3837
this.comboBoxFilter.SelectedItem = this._filter;
3938
this.comboBoxFilter.SelectedValueChanged += new EventHandler(comboBoxFilter_SelectedValueChanged);
4039
this._tnav = new TabNavigator(this);
4140

4241
_recentFindStrings = new MostRecentlyUsed();
43-
_recentFindStrings.RecentItemSelected += OnRecentFindSelected;
4442
_recentFindCombo = new RecentlyUsedComboBox(_recentFindStrings, this.comboBoxFind);
4543
_recentFindCombo.SelectFirstItemByDefault = false;
4644

4745
_recentReplaceStrings = new MostRecentlyUsed();
48-
_recentReplaceStrings.RecentItemSelected += OnRecentReplaceSelected;
4946
_recentReplaceCombo = new RecentlyUsedComboBox(_recentReplaceStrings, this.comboBoxReplace);
5047
_recentReplaceCombo.SelectFirstItemByDefault = false;
5148
}
5249

53-
private void OnRecentReplaceSelected(object sender, MostRecentlyUsedEventArgs e)
54-
{
55-
this.comboBoxReplace.Text = e.Selection;
56-
}
57-
58-
private void OnRecentFindSelected(object sender, MostRecentlyUsedEventArgs e)
59-
{
60-
this.comboBoxFind.Text = e.Selection;
61-
}
62-
6350
protected override void OnDpiChanged(DpiChangedEventArgs e)
6451
{
6552
base.OnDpiChanged(e);
6653
this.PerformLayout();
6754
}
68-
69-
void comboBoxFind_LostFocus(object sender, EventArgs e)
70-
{
71-
return;
72-
}
73-
55+
7456
void comboBoxFilter_SelectedValueChanged(object sender, EventArgs e)
7557
{
7658
this._filter = (SearchFilter)this.comboBoxFilter.SelectedItem;
@@ -125,9 +107,9 @@ void OnNotFound()
125107
this.comboBoxFind.Focus();
126108
}
127109

128-
void OnFindDone()
110+
void OnFindDone(bool didReplace)
129111
{
130-
MessageBox.Show(this.Window, SR.FindNextDonePrompt, SR.ReplaceCompleteCaption, MessageBoxButtons.OK, MessageBoxIcon.Information);
112+
MessageBox.Show(this.Window, SR.FindNextDonePrompt, didReplace ? SR.ReplaceCompleteCaption : SR.FindErrorCaption, MessageBoxButtons.OK, MessageBoxIcon.Information);
131113
this.comboBoxFind.Focus();
132114
}
133115

@@ -154,12 +136,8 @@ void DoReplace()
154136
try
155137
{
156138
string replacement = this.comboBoxReplace.Text;
157-
if (!string.IsNullOrWhiteSpace(replacement))
158-
{
159-
this._recentReplaceStrings.AddItem(replacement);
160-
}
161-
_target.ReplaceCurrent(replacement);
162-
FindNext(false);
139+
bool didReplace = _target.ReplaceCurrent(replacement);
140+
FindNext(false, didReplace);
163141
}
164142
catch (Exception ex)
165143
{
@@ -197,7 +175,7 @@ void buttonReplaceAll_Click(object sender, EventArgs e)
197175
}
198176
}
199177

200-
bool FindNext(bool quiet)
178+
bool FindNext(bool quiet, bool didReplace = false)
201179
{
202180

203181
FindFlags flags = FindFlags.Normal;
@@ -208,6 +186,10 @@ bool FindNext(bool quiet)
208186
if (this.radioButtonUp.Checked) flags |= FindFlags.Backwards;
209187

210188
string expr = this.Expression;
189+
if (expr.Contains(" ") && this.checkBoxWholeWord.Checked)
190+
{
191+
throw new Exception(SR.FindWholeWordOnlyFindsWords);
192+
}
211193
this._recentFindStrings.AddItem(expr);
212194

213195
_lastFlags = flags;
@@ -220,13 +202,17 @@ bool FindNext(bool quiet)
220202
}
221203
if (!quiet)
222204
{
205+
if (didReplace && rc == FindResult.None)
206+
{
207+
rc = FindResult.NoMore;
208+
}
223209
if (rc == FindResult.None)
224210
{
225211
OnNotFound();
226212
}
227213
else if (rc == FindResult.NoMore)
228214
{
229-
OnFindDone();
215+
OnFindDone(didReplace);
230216
}
231217
}
232218
return rc == FindResult.Found;
@@ -261,7 +247,7 @@ public void FindAgain(bool reverse)
261247
}
262248
else if (rc == FindResult.NoMore)
263249
{
264-
OnFindDone();
250+
OnFindDone(false);
265251
}
266252
}
267253
catch (Exception ex)

src/Model/FindTarget.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
using System;
12
using System.Drawing;
23
using System.Xml;
34

45
namespace XmlNotepad
56
{
6-
7+
[Flags]
78
public enum FindFlags {
89
Normal = 0,
910
Regex = 1,

src/UnitTests/AccessibleWrapper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ internal void ClearFindCheckBoxes()
653653
UseWholeWord = false;
654654
UseRegex = false;
655655
UseXPath = false;
656+
FindString = "";
656657
}
657658

658659
private bool GetCheckedState(string name)

src/UnitTests/UnitTest1.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,7 @@ public void TestXPathFind()
13501350
w.SendKeystrokes("^Ipi");
13511351

13521352
FindDialog fd = OpenFindDialog();
1353+
fd.ClearFindCheckBoxes();
13531354
fd.UseXPath = true;
13541355

13551356
AssertNormalizedEqual(fd.FindString, "/processing-instruction('pi')"); // test pi
@@ -1552,6 +1553,7 @@ public void TestFind()
15521553

15531554
Trace.WriteLine("Test illegal regular expressions.");
15541555
findDialog = OpenFindDialog();
1556+
findDialog.UseRegex = false; // make sure %e turns it on!
15551557
findDialog.Window.DismissPopUp("\\%e{ENTER}");
15561558
popup = findDialog.Window.ExpectingPopup("Find Error");
15571559
popup.DismissPopUp("{ENTER}");
@@ -1593,6 +1595,7 @@ public void TestReplace()
15931595

15941596
w.SendKeystrokes("{HOME}");
15951597
var findDialog = OpenReplaceDialog();
1598+
findDialog.ClearFindCheckBoxes();
15961599

15971600
Trace.WriteLine("Toggle dialog using ctrl+f & ctrl+h");
15981601
findDialog.Window.SendKeystrokes("^f");
@@ -1637,13 +1640,16 @@ modified or altered in any way.
16371640
findDialog.Window.SendKeystrokes("%r"); // make the second change
16381641
popup = findDialog.Window.ExpectingPopup("Replace Complete");
16391642
popup.DismissPopUp("{ENTER}");
1643+
findDialog.Window.DismissPopUp("{ESC}");
16401644

1645+
// hack: weird windows 11 bug causes a focus problem after editing a node
1646+
// the Escape key fixes it.
1647+
window.SendKeystrokes("{ESC}");
16411648
CheckOuterXml(@"
16421649
The XXXXX markup in this version is Copyright © 1999 Jon Bosak.
16431650
This work may freely be distributed on condition that it not be
16441651
modified or altered in any way.
16451652
");
1646-
findDialog.Window.DismissPopUp("{ESC}");
16471653

16481654
Undo();
16491655
Undo(); // should move us back to the previous paragraph
@@ -1671,6 +1677,7 @@ public void TestReplaceBackwards()
16711677

16721678
w.SendKeystrokes("{HOME}");
16731679
var findDialog = OpenReplaceDialog();
1680+
findDialog.ClearFindCheckBoxes();
16741681

16751682
Trace.WriteLine("Test we can replace 2 things in backwards sequence");
16761683
w.SendKeystrokes("{HOME}");
@@ -1682,6 +1689,10 @@ public void TestReplaceBackwards()
16821689
var popup = findDialog.Window.ExpectingPopup("Replace Complete");
16831690
popup.DismissPopUp("{ENTER}");
16841691
findDialog.Window.DismissPopUp("{ESC}");
1692+
1693+
// hack: weird windows 11 bug causes a focus problem after editing a node
1694+
// the Escape key fixes it.
1695+
window.SendKeystrokes("{ESC}");
16851696
CheckOuterXml(@"XXXXX version by Jon Bosak, 1996-1999.");
16861697

16871698
Undo();

src/UnitTests/saved.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!---->
3+
<!--Before-->
4+
<?PI6 Before?>
5+
<element7>memory
6+
memory
7+
memory</element7>
8+
<?PI5 After?>
9+
<!--memory-->

src/XmlNotepad/NodeTextView.cs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class NodeTextView : UserControl, IEditableView
2525
private Point _scrollPosition;
2626
private TextEditorOverlay _editor;
2727
private TreeNodeCollection _nodes;
28+
private string _originalText;
2829

2930
public event EventHandler<TreeViewEventArgs> AfterSelect;
3031

@@ -425,6 +426,7 @@ public bool BeginEdit(string value)
425426
{
426427
// see if control has possible values that cannot be known in xsd
427428
IIntellisenseProvider provider = this.IntellisenseProvider;
429+
this._originalText = value != null ? value : this._selectedNode.Text;
428430
string text = value != null ? value : GetNodeText(this._selectedNode);
429431
if (provider != null)
430432
{
@@ -449,19 +451,66 @@ public bool IsEditing
449451
get { return this._editor.IsEditing; }
450452
}
451453

454+
int TranslateToTextEditorOffset(int offset)
455+
{
456+
// the NormalizeNewLines messes with offsets computed from the XmlNode Value
457+
// which may not have these windows style newlines. This method corrects for that.
458+
string xmlValue = this._originalText;
459+
if (!string.IsNullOrEmpty(xmlValue))
460+
{
461+
int xlen = xmlValue.Length;
462+
string textBoxValue = this._editor.Text;
463+
int len = textBoxValue.Length;
464+
for (int i = 0, j = 0; i < len && j < xlen && j < offset; i++, j++)
465+
{
466+
char a = xmlValue[j];
467+
char b = textBoxValue[i];
468+
if (a == '\n' && b == '\r')
469+
{
470+
offset++;
471+
i++;
472+
}
473+
}
474+
}
475+
return offset;
476+
}
477+
478+
int TranslateToXmlOffset(int offset)
479+
{
480+
// This method does the inverse of TranslateToTextEditorOffset.
481+
string xmlValue = this._originalText;
482+
if (!string.IsNullOrEmpty(xmlValue))
483+
{
484+
int xlen = xmlValue.Length;
485+
string textBoxValue = this._editor.Text;
486+
int len = textBoxValue.Length;
487+
for (int i = 0, j = 0; i < len && j < xlen; i++, j++)
488+
{
489+
char a = xmlValue[j];
490+
char b = textBoxValue[i];
491+
if (a == '\n' && b == '\r')
492+
{
493+
offset--;
494+
i++;
495+
}
496+
}
497+
}
498+
return offset;
499+
}
500+
452501
public void SelectText(int index, int length)
453502
{
454503
if (this._editor.IsEditing)
455504
{
456-
this._editor.Select(index, length);
505+
this._editor.Select(TranslateToTextEditorOffset(index), length);
457506
}
458507
}
459508

460509
public bool ReplaceText(int index, int length, string replacement)
461510
{
462511
if (this._editor.IsEditing)
463512
{
464-
bool rc = this._editor.Replace(index, length, replacement);
513+
bool rc = this._editor.Replace(TranslateToTextEditorOffset(index), length, replacement);
465514
this._editor.EndEdit(false);
466515
return rc;
467516
}
@@ -486,7 +535,7 @@ public bool EndEdit(bool cancel)
486535
return this._editor.EndEdit(cancel);
487536
}
488537

489-
public int SelectionStart { get { return this._editor.SelectionStart; } }
538+
public int SelectionStart { get { return TranslateToXmlOffset(this._editor.SelectionStart); } }
490539

491540
public int SelectionLength { get { return this._editor.SelectionLength; } }
492541

@@ -878,7 +927,7 @@ void FindString(object sender, string toFind)
878927
TreeNode start = node;
879928
while (node != null)
880929
{
881-
string s = GetNodeText(node);
930+
string s = node.Text.Trim();
882931
if (s != null && s.StartsWith(toFind, StringComparison.CurrentCultureIgnoreCase))
883932
{
884933
this.SelectedNode = node;

0 commit comments

Comments
 (0)