Skip to content

Address issue #452: Selection character/word count#460

Merged
schuyler merged 3 commits into
mainfrom
claude/pensive-brahmagupta-n48lkv
Jun 22, 2026
Merged

Address issue #452: Selection character/word count#460
schuyler merged 3 commits into
mainfrom
claude/pensive-brahmagupta-n48lkv

Conversation

@schuyler

Copy link
Copy Markdown
Owner

Summary

Adds a live count of the current editor selection to the existing bottom count widget. When text is selected, the widget shows the selection's words, characters, and characters (no spaces) — each title carrying a "selected" indicator. When the selection is empty, it reverts to the document-wide totals exactly as before.

This lets users writing length-limited content (e.g. command entries) verify selection length without leaving the app.

Approach

  • Reuses the existing three-item count popup rather than adding new UI, so all three count types are supported automatically and honor whichever type the user has selected in the dropdown. Respects the editorShowWordCount preference and reuses the existing localized plural-string machinery.
  • Selection counts are computed from the editor's selected substring via a new public MPTextCountForString() wrapper over the same per-string algorithm used for the document-wide count, guaranteeing consistent results.
  • A showingSelectionCount flag ensures a throttled document updateWordCount cannot clobber the selection display; document-total setters keep storing values but skip title writes while a selection is shown.

Changed files

  • MacDown/Code/Extension/DOMNode+Text.{h,m} — expose MPTextCountForString().
  • MacDown/Code/Document/MPDocument.m — observe NSTextViewDidChangeSelectionNotification; add editorSelectionDidChange: / refreshDocumentWordCountTitles; refactor count-title writing into selected:-aware helpers; nil-editor guard; reset selection mode when the preference is disabled.
  • MacDown/Localization/en.lproj/Localizable.strings — three new "… selected" plural keys (other locales fall back to English until translated).
  • MacDownTests/MPSelectionCountTests.m (new, wired into the Xcode project) — unit tests for the pure counting logic and display-mode coordination.
  • CHANGELOG.md — Unreleased entry.

Related Issue

Related to #452

Manual Testing Plan

  1. Enable the word count widget (View / preferences) so the count popup is visible at the bottom of the editor.
  2. Type some text. With no selection, the popup shows document totals (unchanged behavior).
  3. Select a run of text. The popup should switch to the selection's counts, e.g. "12 words selected"; use the dropdown to confirm words / characters / characters (no spaces) all reflect the selection.
  4. Extend/shrink the selection (shift-arrow, drag) and confirm the count updates live.
  5. Collapse the selection to a caret — the popup reverts to document totals.
  6. Select text that spans newlines and includes leading/trailing spaces; confirm characters excludes newlines and characters-no-spaces excludes all whitespace.
  7. Type while a selection exists (replacing it) and confirm no stale "selected" title lingers.
  8. Toggle the word-count preference off and on; confirm it returns in document-totals mode.

Review Notes

  • Architecture validated against the existing counting code; the change intentionally reuses MPGetStringAccumulatedTextCount so selection and document counts use the same algorithm.
  • Fresh-eyes code review found no build/behavior defects; its one hardening suggestion (explicit nil-editor guard) is included.
  • Tests run on macOS CI only.

Generated by Claude Code

claude added 3 commits June 22, 2026 07:57
When the editor has a non-empty selection, the bottom count widget now
shows the selection's words, characters, and characters-without-spaces
(each title carries a "selected" indicator). When the selection is empty
it reverts to the document-wide totals.

- Expose MPTextCountForString() so the editor can count its selected
  substring using the same algorithm as the document-wide word count.
- Observe NSTextViewDidChangeSelectionNotification and swap the widget
  titles via a showingSelectionCount flag; document-total setters keep
  storing values but skip title writes while a selection is shown so a
  throttled updateWordCount can't clobber the selection display.
- Add localized "… selected" plural strings (en).
- Add MPSelectionCountTests covering the pure counting logic and the
  display-mode coordination.

Related to #452
Make the bare-document path explicit instead of relying on a struct
return from a nil receiver, so editorSelectionDidChange: is well-defined
before the nib loads.

Related to #452
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Report

Current Coverage: 60.76%

Coverage Details (Summary)
Name                                                                                                                                   Coverage            
-------------------------------------------------------------------------------------------------------------------------------------- ------------------- 
MASPreferences.bundle                                                                                                                  0.00% (0/0)         
MacDown 3000.app                                                                                                                       62.06% (8785/14155) 
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Document/MPAsset.m                                                         93.69% (104/111)    
        -[MPAsset typeName]                                                                                                            100.00% (3/3)       
        -[MPAsset defaultTypeName]                                                                                                     100.00% (3/3)       
        +[MPAsset assetWithURL:andType:]                                                                                               100.00% (3/3)       
        -[MPAsset initWithURL:andType:]                                                                                                87.50% (7/8)        
        -[MPAsset init]                                                                                                                100.00% (3/3)       
        -[MPAsset templateForOption:]                                                                                                  0.00% (0/6)         
        -[MPAsset htmlForOption:]                                                                                                      100.00% (28/28)     
        -[MPStyleSheet defaultTypeName]                                                                                                100.00% (3/3)       
        +[MPStyleSheet CSSWithURL:]                                                                                                    100.00% (3/3)       
        -[MPStyleSheet templateForOption:]                                                                                             100.00% (20/20)     
        -[MPScript defaultTypeName]                                                                                                    100.00% (3/3)       
        +[MPScript javaScriptWithURL:]                                                                                                 100.00% (3/3)       
        -[MPScript templateForOption:]                                                                                                 100.00% (20/20)     
        -[MPEmbeddedScript htmlForOption:]                                                                                             100.00% (5/5)       
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Extension/DOMNode+Text.m                                                   100.00% (111/111)   
        MPAccumulatedTextCountMake                                                                                                     100.00% (7/7)       
        MPAccumulatedTextCountZero                                                                                                     100.00% (3/3)       
        MPAccumulatedTextCountAdd                                                                                                      100.00% (5/5)       
        MPGetStringAccumulatedTextCount                                                                                                100.00% (34/34)     
        __MPGetStringAccumulatedTextCount_block_invoke                                                                                 100.00% (3/3)       
        __MPGetStringAccumulatedTextCount_block_invoke.5                                                                               100.00% (4/4)       
        MPGetChildrenAccumulatedTextCount                                                                                              100.00% (6/6)       
        MPGetNodeAccumulatedTextCount                                                                                                  100.00% (32/32)     
        MPTextCountForString                                                                                                           100.00% (9/9)       
        -[DOMNode(Text) textCount]                                                                                                     100.00% (8/8)       
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Utility/FileURLInlining.m                                                  26.60% (25/94)      
        +[FileURLInlining inlineFromIterable:]                                                                                         0.00% (0/11)        
        -[FileURLInlining initWithURL:]                                                                                                0.00% (0/6)         
        -[FileURLInlining init]                                                                                                        100.00% (3/3)       
        -[FileURLInlining inlineContent]                                                                                               0.00% (0/9)         
        -[FileURLInlining imageContent]                                                                                                0.00% (0/12)        
        +[FileURLInlining mimeTypeForFilePath:]                                                                                        100.00% (22/22)     
        -[FileURLInlining clippingContent]                                                                                             0.00% (0/4)         
        +[FileURLInlining isTextClippingAtPath:]                                                                                       0.00% (0/12)        
        +[FileURLInlining textContentFromClipping:]                                                                                    0.00% (0/15)        
    /Users/runner/work/macdown3000/macdown3000/Dependency/peg-markdown-highlight/HGMarkdownHighlightingStyle.m                         87.69% (57/65)      
        +[HGMarkdownHighlightingStyle colorFromARGBColor:]                                                                             100.00% (6/6)       
        -[HGMarkdownHighlightingStyle initWithType:attributesToAdd:toRemove:fontTraitsToAdd:]                                          88.89% (8/9)        
        -[HGMarkdownHighlightingStyle initWithStyleAttributes:baseFont:]                                                               86.00% (43/50)      
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Utility/MPResourceWatcherSet.m                                             93.62% (88/94)      
        -[MPResourceWatcherSet init]                                                                                                   100.00% (6/6)       
        -[MPResourceWatcherSet dealloc]                                                                                                0.00% (0/3)         
        -[MPResourceWatcherSet watchedPaths]                                                                                           100.00% (3/3)       
        -[MPResourceWatcherSet updateWatchedPaths:]                                                                                    100.00% (16/16)     
        -[MPResourceWatcherSet addWatcherForPath:]                                                                                     100.00% (32/32)     

... (3215 more lines truncated)

📊 **Full coverage report available in workflow artifacts**

@schuyler schuyler merged commit 2c84442 into main Jun 22, 2026
10 checks passed
@schuyler schuyler deleted the claude/pensive-brahmagupta-n48lkv branch June 22, 2026 08:27
schuyler added a commit that referenced this pull request Jul 1, 2026
## Summary

PR #460 added a selection character-count feature to the word-count
widget and introduced three new plural string keys, but only in
`en.lproj`. In any other locale, `NSLocalizedString` returns the **key
name** (not an English fallback) when the key is absent, so
`JJPluralForm` can't parse it and the widget shows **"ERR"** when text
is selected. Reported by rcuisnier in French during rc.1 testing.

This adds the three keys to all 25 non-English locales:

- `WORDS_SELECTED_PLURAL_STRING`
- `CHARACTERS_SELECTED_PLURAL_STRING`
- `CHARACTERS_NO_SPACES_SELECTED_PLURAL_STRING`

## Approach

Each locale's translation was built from its **existing** base plural
vocabulary (`WORDS_PLURAL_STRING`, etc.) plus the word for "selected,"
with grammatical agreement matched per language — gender/number in
Romance, Slavic, Hebrew, Arabic, Icelandic; case agreement in Finnish;
invariant participles where appropriate (German, Dutch, Turkish, Hindi,
CJK). The two-form `"%@ singular;%@ plural"` structure and
`JJ_PLURAL_FORM_RULE = "1"` convention are preserved.

A few forms were refined after review:
- **Japanese** uses the state form `選択中` ("currently selected") rather
than a verb reading as a command.
- **Arabic / Hebrew** place the participle adjacent to the noun; Hebrew
uses `מסומן` ("marked/selected") with correct final-vs-medial nun across
gender/number.

## Verification

- `grep -L` confirms all 25 non-English files now contain the keys; none
missing.
- `en.lproj` unchanged; no existing keys altered; only the three
additions per file (`25 files changed, 175 insertions`).
- Non-Latin strings decoded to Unicode codepoints to confirm correct
letters and logical byte order (independent of terminal shaping/BiDi).

Related to #452
schuyler added a commit that referenced this pull request Jul 1, 2026
## Summary

PR #460 added a selection character-count feature to the word-count
widget and introduced three new plural string keys, but only in
`en.lproj`. In any other locale, `NSLocalizedString` returns the **key
name** (not an English fallback) when the key is absent, so
`JJPluralForm` can't parse it and the widget shows **"ERR"** when text
is selected. Reported by rcuisnier in French during rc.1 testing.

This adds the three keys to all 25 non-English locales:

- `WORDS_SELECTED_PLURAL_STRING`
- `CHARACTERS_SELECTED_PLURAL_STRING`
- `CHARACTERS_NO_SPACES_SELECTED_PLURAL_STRING`

## Approach

Each locale's translation was built from its **existing** base plural
vocabulary (`WORDS_PLURAL_STRING`, etc.) plus the word for "selected,"
with grammatical agreement matched per language — gender/number in
Romance, Slavic, Hebrew, Arabic, Icelandic; case agreement in Finnish;
invariant participles where appropriate (German, Dutch, Turkish, Hindi,
CJK). The two-form `"%@ singular;%@ plural"` structure and
`JJ_PLURAL_FORM_RULE = "1"` convention are preserved.

A few forms were refined after review:
- **Japanese** uses the state form `選択中` ("currently selected") rather
than a verb reading as a command.
- **Arabic / Hebrew** place the participle adjacent to the noun; Hebrew
uses `מסומן` ("marked/selected") with correct final-vs-medial nun across
gender/number.

## Verification

- `grep -L` confirms all 25 non-English files now contain the keys; none
missing.
- `en.lproj` unchanged; no existing keys altered; only the three
additions per file (`25 files changed, 175 insertions`).
- Non-Latin strings decoded to Unicode codepoints to confirm correct
letters and logical byte order (independent of terminal shaping/BiDi).

Related to #452
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants