Skip to content

Commit d91e5d3

Browse files
Improve word count: real-time updates, better perf, and formatted display
- Propagate TextChanging event through INotepadsCore/NotepadsCore so word count updates in real time as the user types, deletes, or pastes text - Replace string.Split word counting with O(n) char iteration to avoid intermediate allocations on every keystroke for large files - Format display as Words: 1,245 and Words: 1,245 | Selected: 132 with locale-aware thousand separators Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 5de10f2 commit d91e5d3

4 files changed

Lines changed: 51 additions & 6 deletions

File tree

src/Notepads/Core/INotepadsCore.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// ---------------------------------------------------------------------------------------------
1+
// ---------------------------------------------------------------------------------------------
22
// Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved.
33
// See LICENSE file in the project root for license information.
44
// ---------------------------------------------------------------------------------------------
@@ -31,6 +31,7 @@ public interface INotepadsCore
3131
event EventHandler<ITextEditor> TextEditorFontZoomFactorChanged;
3232
event EventHandler<ITextEditor> TextEditorEncodingChanged;
3333
event EventHandler<ITextEditor> TextEditorLineEndingChanged;
34+
event EventHandler<ITextEditor> TextEditorTextChanging;
3435
event EventHandler<ITextEditor> TextEditorModeChanged;
3536
event EventHandler<ITextEditor> TextEditorMovedToAnotherAppInstance;
3637
event EventHandler<IReadOnlyList<IStorageItem>> StorageItemsDropped;

src/Notepads/Core/NotepadsCore.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// ---------------------------------------------------------------------------------------------
1+
// ---------------------------------------------------------------------------------------------
22
// Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved.
33
// See LICENSE file in the project root for license information.
44
// ---------------------------------------------------------------------------------------------
@@ -42,6 +42,7 @@ public class NotepadsCore : INotepadsCore
4242
public event EventHandler<ITextEditor> TextEditorFontZoomFactorChanged;
4343
public event EventHandler<ITextEditor> TextEditorEncodingChanged;
4444
public event EventHandler<ITextEditor> TextEditorLineEndingChanged;
45+
public event EventHandler<ITextEditor> TextEditorTextChanging;
4546
public event EventHandler<ITextEditor> TextEditorModeChanged;
4647
public event EventHandler<ITextEditor> TextEditorMovedToAnotherAppInstance;
4748
public event EventHandler<IReadOnlyList<IStorageItem>> StorageItemsDropped;
@@ -199,6 +200,7 @@ public ITextEditor CreateTextEditor(
199200
textEditor.Loaded += TextEditor_Loaded;
200201
textEditor.Unloaded += TextEditor_Unloaded;
201202
textEditor.SelectionChanged += TextEditor_OnSelectionChanged;
203+
textEditor.TextChanging += TextEditor_OnTextChanging;
202204
textEditor.FontZoomFactorChanged += TextEditor_OnFontZoomFactorChanged;
203205
textEditor.KeyDown += TextEditorKeyDown;
204206
textEditor.ModificationStateChanged += TextEditor_OnEditorModificationStateChanged;
@@ -238,6 +240,7 @@ public void DeleteTextEditor(ITextEditor textEditor)
238240
textEditor.Unloaded -= TextEditor_Unloaded;
239241
textEditor.KeyDown -= TextEditorKeyDown;
240242
textEditor.SelectionChanged -= TextEditor_OnSelectionChanged;
243+
textEditor.TextChanging -= TextEditor_OnTextChanging;
241244
textEditor.FontZoomFactorChanged -= TextEditor_OnFontZoomFactorChanged;
242245
textEditor.ModificationStateChanged -= TextEditor_OnEditorModificationStateChanged;
243246
textEditor.ModeChanged -= TextEditor_OnModeChanged;
@@ -521,6 +524,12 @@ private void TextEditor_OnSelectionChanged(object sender, EventArgs e)
521524
TextEditorSelectionChanged?.Invoke(this, textEditor);
522525
}
523526

527+
private void TextEditor_OnTextChanging(object sender, EventArgs e)
528+
{
529+
if (!(sender is ITextEditor textEditor)) return;
530+
TextEditorTextChanging?.Invoke(this, textEditor);
531+
}
532+
524533
private void TextEditor_OnFontZoomFactorChanged(object sender, EventArgs e)
525534
{
526535
if (!(sender is ITextEditor textEditor)) return;

src/Notepads/Views/MainPage/NotepadsMainPage.StatusBar.cs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,18 +173,52 @@ private void UpdateWordCountIndicator(ITextEditor textEditor)
173173
if (!string.IsNullOrEmpty(selectedText))
174174
{
175175
var selectedWordCount = CountWords(selectedText);
176-
WordCountIndicator.Text = $"{selectedWordCount}/{totalWordCount} words";
176+
WordCountIndicator.Text = string.Format(
177+
"Words: {0} | Selected: {1}",
178+
totalWordCount.ToString("N0", CultureInfo.CurrentCulture),
179+
selectedWordCount.ToString("N0", CultureInfo.CurrentCulture));
177180
}
178181
else
179182
{
180-
WordCountIndicator.Text = $"{totalWordCount} words";
183+
WordCountIndicator.Text = string.Format(
184+
"Words: {0}",
185+
totalWordCount.ToString("N0", CultureInfo.CurrentCulture));
181186
}
182187
}
183188

189+
/// <summary>
190+
/// Counts words in the given text using character iteration for efficiency.
191+
/// A word is defined as a contiguous sequence of non-whitespace characters.
192+
/// </summary>
184193
private static int CountWords(string text)
185194
{
186-
if (string.IsNullOrWhiteSpace(text)) return 0;
187-
return text.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Length;
195+
if (string.IsNullOrEmpty(text)) return 0;
196+
197+
int wordCount = 0;
198+
bool inWord = false;
199+
200+
for (int i = 0; i < text.Length; i++)
201+
{
202+
if (char.IsWhiteSpace(text[i]))
203+
{
204+
if (inWord)
205+
{
206+
wordCount++;
207+
inWord = false;
208+
}
209+
}
210+
else
211+
{
212+
inWord = true;
213+
}
214+
}
215+
216+
if (inWord)
217+
{
218+
wordCount++;
219+
}
220+
221+
return wordCount;
188222
}
189223

190224
private void UpdateFontZoomIndicator(ITextEditor textEditor)

src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ private INotepadsCore NotepadsCore
6363
_notepadsCore.TextEditorMovedToAnotherAppInstance += OnTextEditorMovedToAnotherAppInstance;
6464
_notepadsCore.TextEditorRenamed += (sender, editor) => { if (NotepadsCore.GetSelectedTextEditor() == editor) SetupStatusBar(editor); };
6565
_notepadsCore.TextEditorSelectionChanged += (sender, editor) => { if (NotepadsCore.GetSelectedTextEditor() == editor) { UpdateLineColumnIndicator(editor); UpdateWordCountIndicator(editor); } };
66+
_notepadsCore.TextEditorTextChanging += (_, editor) => { if (NotepadsCore.GetSelectedTextEditor() == editor) UpdateWordCountIndicator(editor); };
6667
_notepadsCore.TextEditorFontZoomFactorChanged += (sender, editor) => { if (NotepadsCore.GetSelectedTextEditor() == editor) UpdateFontZoomIndicator(editor); };
6768
_notepadsCore.TextEditorEncodingChanged += (sender, editor) => { if (NotepadsCore.GetSelectedTextEditor() == editor) UpdateEncodingIndicator(editor.GetEncoding()); };
6869
_notepadsCore.TextEditorLineEndingChanged += (sender, editor) => { if (NotepadsCore.GetSelectedTextEditor() == editor) { UpdateLineEndingIndicator(editor.GetLineEnding()); UpdateLineColumnIndicator(editor); } };

0 commit comments

Comments
 (0)