Add AcceptanceGranularity setting (Word / Phrase / Full) for primary accept key#388
Merged
FuJacob merged 2 commits intoMay 30, 2026
Merged
Conversation
…y accept Fixes FuJacob#10. The primary accept key (default Tab) now honors a user-selected granularity: Word (current behavior), Phrase (Cursor-style — stop at .!?\n or their quoted/bracketed equivalents), or Full Suggestion (same outcome as the dedicated full-accept key). The dedicated full-accept key remains an unconditional per-press override. The new nextAcceptancePhrase chunker composes over the existing nextAcceptanceChunk so word-boundary, internal-punctuation, and leading-whitespace policy stay identical across multi-word seams. Two extra rules make sentence boundaries work robustly: - newlines are scanned inside each composed chunk (the chunker returns leading whitespace including \n, so a tail like "hello\nworld" would otherwise carry the newline forward unnoticed) - terminator detection walks back past closing quotes and brackets so "done." Next stops at the closing quote rather than over-accepting Known limitation: a tail ending in U.S.A. is treated as a sentence end (early break). Rule-based scanners can't disambiguate without NLP; Cursor and Copilot behave the same way. Per-app and per-profile granularity stay out of scope for this PR (touches the rules system — follow-up issue).
…ase test Addresses review feedback on FuJacob#388: - prepareAcceptance: replace preconditionFailure on the .full arm with assertionFailure + an .invalid return so a future mis-route degrades gracefully in release instead of crashing, matching the other .invalid branches. - Extract the duplicated Acceptance Mode picker and binding from SettingsView and ShortcutsPaneView into a shared AcceptanceModePickerView. - Add an integration test driving prepareAcceptance(granularity: .phrase) through nextAcceptancePhrase.
Contributor
Author
|
Pushed 24233cf addressing the three points:
Build and the full test suite pass (450 tests), SwiftLint is clean, and the project file was regenerated with |
Owner
|
Thanks @rpickmans will take a look tonight! :) |
FuJacob
approved these changes
May 30, 2026
Owner
FuJacob
left a comment
There was a problem hiding this comment.
LGTM thank you for your hard work!! :)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a user-selectable acceptance granularity to the primary accept key (default Tab): Word (current behavior, default), Phrase (stops at
.!?\nor their quoted/bracketed equivalents — Cursor-style), or Full Suggestion (same outcome as the dedicated full-accept key). The dedicated full-accept key (default`) remains an unconditional per-press override regardless of the chosen granularity.The new
nextAcceptancePhrasechunker composes over the existingnextAcceptanceChunk, so word-boundary, internal-punctuation, and leading-whitespace policy stay identical across multi-word seams. Two extra rules make sentence boundaries robust:nextAcceptanceChunkreturns leading whitespace including\n, so a tail likehello\nworldwould otherwise carry the newline forward unnoticed."done." Nextstops at the closing quote rather than over-accepting into the next sentence. Handles",',),],}, and curly quotes”,’.Known limitation: a tail ending in
U.S.A.is treated as a sentence end (early break). Rule-based scanners can't disambiguate this without NLP; Cursor and Copilot behave the same way. Covered by a dedicated test that documents the behavior.Validation
18 new
nextAcceptancePhrasetests cover empty tail, no-terminator,.!?boundaries, newline-between-tokens, leading-newline, multiple newlines, leading whitespace, abbreviation false-break (documented as known limitation),autoAcceptTrailingPunctuationflag invariance, interior punctuation preservation, straight/curly quoted sentence ends, parenthesized sentence ends, nested closers, and bare-closing-quote (no false break).Linked issues
Fixes #10
Risk / rollout notes
.word, preserving current Tab behavior. The setting is read once per accept call from the value-typed snapshot, so a mid-press toggle in Settings can't strand a partially-handled press.SettingsView.shortcutsSectionand the redesignedShortcutsPaneViewso the setting is reachable regardless of which Settings shell is active.WelcomeView.swift:303) and the parallel label in the other settings pane. The new "Acceptance Mode" Picker disambiguates what the key actually does. If you'd prefer a dynamic row label ("Accept Word" / "Accept Phrase" / "Accept Full") to match the picker selection, happy to make that change in this PR or a follow-up.prepareAcceptancesignature change (added requiredgranularity:parameter, no default) — only 2 in-tree callers (coordinator + state-helper test); both updated..fullis precondition-rejected inprepareAcceptancebecause the coordinator routes full accepts toprepareFullAcceptancefor the distinct invalid-reason message; defensive handling could be substituted if you'd prefer.Greptile Summary
This PR adds a user-selectable
AcceptanceGranularitysetting (Word / Phrase / Full) for the primary accept key, while keeping the dedicated full-accept key as an unconditional per-press override. The newnextAcceptancePhrasechunker composes overnextAcceptanceChunk, inheriting existing word-boundary and leading-whitespace policy, with extra rules for newlines (which appear as leading whitespace in the composed chunks) and closing-punctuation walk-back for quoted/parenthesised sentence ends.AcceptanceGranularityis persisted in UserDefaults with a.wordfallback, wired into the settings snapshot publisher via an outerCombineLatest, and exposed in both the legacySettingsViewand the redesignedShortcutsPaneViewthrough a new sharedAcceptanceModePickerViewcomponent.prepareAcceptancesignature gains a requiredgranularity:parameter;.fullis guarded withassertionFailure+ graceful fallback since the coordinator already routes full accepts toprepareFullAcceptance. 18 new unit tests cover the chunker in isolation; a new integration test inSuggestionStateHelperTestsconfirms the.phrasebranch propagates correctly throughprepareAcceptanceend-to-end.Confidence Score: 5/5
Safe to merge — the new phrase chunker composes cleanly over the existing word chunker, default behavior is unchanged for existing installs, and all previously flagged issues have been addressed.
The core logic in nextAcceptancePhrase is well-reasoned: newline detection correctly captures
from leading-whitespace chunks, the closing-punctuation walk-back handles nested quoted/parenthesised sentence ends, and autoAcceptTrailingPunctuation invariance holds. The .full guard now uses assertionFailure + graceful .invalid return instead of preconditionFailure, matching every other invalid branch. The integration test for .phrase in prepareAcceptance closes the only gap from the prior review round. No new behavioral regressions are introduced; the .word default preserves all existing acceptance behaviour for users who never touch the new setting.
No files require special attention.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Key press detected] --> B{fullText flag\nor granularity == .full?} B -- Yes --> C[prepareFullAcceptance\nreturns entire remainingText] B -- No --> D{granularity} D -- .word --> E[nextAcceptanceChunk\none word + optional trailing punct] D -- .phrase --> F[nextAcceptancePhrase\ncompose chunks until .!? or newline] F --> G{chunk contains newline?} G -- Yes --> H[Accumulate up to newline inclusive\nreturn early] G -- No --> I[Accumulate chunk\nadvance working] I --> J{endsInSentenceTerminator?\nwalk back past closing punct} J -- Yes --> K[Return phrase] J -- No --> F E --> L[guard chunk not empty] K --> L H --> L L -- empty --> M[.invalid — key passes through] L -- non-empty --> N[.ready liveContext / session / acceptedChunk] N --> O[Coordinator calls commitAcceptedChunk]Comments Outside Diff (1)
Cotabby/UI/Settings/Panes/ShortcutsPaneView.swift, line 381-386 (link)The five-line
Pickerblock and itsacceptanceGranularityBindinghelper are replicated character-for-character inSettingsView.swift. If the labels or cases change (e.g., renaming "Full Suggestion" or adding a fourth mode), both files need identical edits. Extracting this into a small privateAcceptanceModePickerViewor a@ViewBuilderhelper shared by both would remove the drift risk entirely.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Reviews (2): Last reviewed commit: "Harden AcceptanceGranularity: graceful ...." | Re-trigger Greptile