Skip to content

Commit 9ef1537

Browse files
ctardyclaude
andcommitted
Add occurrence highlighting on text selection
When the user selects text (2+ chars), all other matching occurrences in the document are highlighted. Updates architecture docs in CLAUDE.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f8756e6 commit 9ef1537

3 files changed

Lines changed: 112 additions & 2 deletions

File tree

CLAUDE.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,19 @@ src/
3737
├── Program.cs Entry point, .tmp swap, password flow
3838
├── Storage.cs Binary marker, read/write encrypted payload
3939
├── Crypto.cs AES-256-CBC + HMAC-SHA256, PBKDF2
40+
├── Settings.cs User settings (theme, save-on-close)
41+
├── Theme.cs Dark/light theme system, color palette
4042
├── EditorForm.cs Main editor window, menus, shortcuts
43+
├── LineNumberTextBox.cs RichTextBox with line numbers + occurrence highlighting
44+
├── SearchBar.cs Ctrl+F find panel
45+
├── TabBar.cs Tab strip for multi-note support
46+
├── TabStore.cs Tab serialization/deserialization
47+
├── NoteTab.cs Single note tab model
4148
├── CreatePasswordDialog.cs Password creation (entry + confirm)
4249
├── UnlockDialog.cs Password prompt (5 attempts max)
43-
└── SearchBar.cs Ctrl+F find panel
50+
├── CloseConfirmDialog.cs Save-on-close confirmation dialog
51+
├── GoToLineDialog.cs Ctrl+G go-to-line dialog
52+
├── RenameTabDialog.cs Tab rename dialog
53+
├── SettingsDialog.cs Settings UI (theme, behavior)
54+
└── Updater.cs Auto-update check via GitHub releases
4455
```

src/LineNumberTextBox.cs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Drawing;
45
using System.Windows.Forms;
@@ -25,8 +26,13 @@ class LineNumberTextBox : UserControl
2526
{
2627
BufferedPanel gutter;
2728
RichTextBox rtb;
29+
Timer highlightTimer;
30+
bool suppressEvents;
31+
string lastHighlightedWord;
32+
List<int> highlightPositions = new List<int>();
2833

2934
const int GutterPadding = 6;
35+
const int MinHighlightLength = 2;
3036

3137
public event EventHandler ContentChanged;
3238

@@ -56,7 +62,13 @@ public LineNumberTextBox()
5662
rtb.TextChanged += (s, e) =>
5763
{
5864
gutter.Invalidate();
59-
if (ContentChanged != null) ContentChanged(this, EventArgs.Empty);
65+
if (!suppressEvents)
66+
{
67+
// Text changed by user — clear stale highlights
68+
highlightPositions.Clear();
69+
lastHighlightedWord = null;
70+
if (ContentChanged != null) ContentChanged(this, EventArgs.Empty);
71+
}
6072
};
6173
rtb.VScroll += (s, e) => gutter.Invalidate();
6274
rtb.Resize += (s, e) => gutter.Invalidate();
@@ -97,6 +109,11 @@ public LineNumberTextBox()
97109

98110
gutter.Paint += OnGutterPaint;
99111

112+
highlightTimer = new Timer();
113+
highlightTimer.Interval = 200;
114+
highlightTimer.Tick += delegate { highlightTimer.Stop(); ApplyOccurrenceHighlights(); };
115+
rtb.SelectionChanged += delegate { OnSelectionChanged(); };
116+
100117
Controls.Add(rtb);
101118
Controls.Add(gutter);
102119
}
@@ -286,6 +303,85 @@ void OnDragDrop(object sender, DragEventArgs e)
286303
}
287304
}
288305

306+
// ── Occurrence highlighting ──
307+
308+
void OnSelectionChanged()
309+
{
310+
if (suppressEvents) return;
311+
highlightTimer.Stop();
312+
highlightTimer.Start();
313+
}
314+
315+
void ApplyOccurrenceHighlights()
316+
{
317+
string selectedText = rtb.SelectedText;
318+
319+
// Trim trailing newlines that RichTextBox may include
320+
if (selectedText != null)
321+
selectedText = selectedText.TrimEnd('\r', '\n');
322+
323+
// Determine if we should highlight
324+
bool shouldHighlight = selectedText != null
325+
&& selectedText.Length >= MinHighlightLength
326+
&& selectedText.IndexOf('\n') < 0
327+
&& selectedText.IndexOf('\r') < 0
328+
&& selectedText.Trim().Length == selectedText.Length;
329+
330+
string word = shouldHighlight ? selectedText : null;
331+
332+
// Skip if same word is already highlighted
333+
if (word == lastHighlightedWord
334+
|| (word != null && lastHighlightedWord != null && word == lastHighlightedWord))
335+
return;
336+
337+
suppressEvents = true;
338+
int savedSelStart = rtb.SelectionStart;
339+
int savedSelLength = rtb.SelectionLength;
340+
341+
// Remove previous highlights
342+
ClearHighlights();
343+
344+
if (word != null)
345+
{
346+
// Find all occurrences
347+
string text = rtb.Text;
348+
int idx = 0;
349+
while (idx < text.Length)
350+
{
351+
idx = text.IndexOf(word, idx, StringComparison.OrdinalIgnoreCase);
352+
if (idx < 0) break;
353+
// Skip the current selection itself
354+
if (idx != savedSelStart)
355+
{
356+
highlightPositions.Add(idx);
357+
rtb.Select(idx, word.Length);
358+
rtb.SelectionBackColor = Theme.MatchHighlight;
359+
}
360+
idx += word.Length;
361+
}
362+
lastHighlightedWord = word;
363+
}
364+
365+
// Restore selection
366+
rtb.Select(savedSelStart, savedSelLength);
367+
suppressEvents = false;
368+
}
369+
370+
void ClearHighlights()
371+
{
372+
if (highlightPositions.Count > 0 && lastHighlightedWord != null)
373+
{
374+
int wordLen = lastHighlightedWord.Length;
375+
for (int i = 0; i < highlightPositions.Count; i++)
376+
{
377+
rtb.Select(highlightPositions[i], wordLen);
378+
rtb.SelectionBackColor = rtb.BackColor;
379+
}
380+
highlightPositions.Clear();
381+
}
382+
lastHighlightedWord = null;
383+
}
384+
289385
// ── Gutter painting ──
290386

291387
void OnGutterPaint(object sender, PaintEventArgs e)

src/Theme.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ static class Theme
3838
public static Color ButtonText;
3939
public static Color ButtonSecondary;
4040
public static Color ErrorText;
41+
public static Color MatchHighlight;
4142

4243
static Theme()
4344
{
@@ -73,6 +74,7 @@ public static void SetMode(AppTheme mode)
7374
ButtonText = Color.White;
7475
ButtonSecondary = Color.FromArgb(60, 60, 60);
7576
ErrorText = Color.FromArgb(244, 71, 71);
77+
MatchHighlight = Color.FromArgb(80, 80, 0);
7678
}
7779
else
7880
{
@@ -100,6 +102,7 @@ public static void SetMode(AppTheme mode)
100102
ButtonText = Color.White;
101103
ButtonSecondary = Color.FromArgb(225, 225, 225);
102104
ErrorText = Color.FromArgb(200, 40, 40);
105+
MatchHighlight = Color.FromArgb(255, 255, 0);
103106
}
104107
}
105108

0 commit comments

Comments
 (0)