Skip to content

feat(staging): per-line selection + Cmd+S shortcut#9

Merged
poolcamacho merged 3 commits intomasterfrom
feat/staging-line-level
Apr 20, 2026
Merged

feat(staging): per-line selection + Cmd+S shortcut#9
poolcamacho merged 3 commits intomasterfrom
feat/staging-line-level

Conversation

@poolcamacho
Copy link
Copy Markdown
Owner

Summary

Line-level interactive staging. The checkbox on each @@ header still stages the whole hunk, but now every individual + / - line has its own checkbox too — pick exactly which additions and deletions go into the index. Cmd+S stages (or unstages, depending on the view) the current selection without leaving the diff.

What changed

Stage 4 — per-line selection (d2a0576)

  • AppState.selectedHunks: Set<Int> becomes selectedLines: [Int: Set<Int>], keyed by hunk index with values pointing at line indices inside that hunk's lines array. A hunk with every modifiable line selected collapses to the same patch the hunk-only path produced.
  • DiffFile.patchText(forLines:) walks each selected hunk and rewrites the body:
    • Unselected + lines are dropped — they don't exist in the old side and we don't want them in the new side either.
    • Unselected - lines are demoted to context so the line survives the partial patch.
    • Hunk header oldCount / newCount are recomputed from the rewritten body. oldStart / newStart stay anchored.
    • Helper rewriteHunk + emit split out so cyclomatic complexity stays under the linter limit.
  • GitCoordinator.stageSelectedLines / unstageSelectedLines replace the previous hunk-only methods and pipe the rewritten patch to git apply --cached (with --reverse when unstaging).
  • DiffView: every + / - DiffLineView gains an optional isSelected binding with a checkbox. Context rows reserve the slot so columns stay aligned. The @@ header checkbox still toggles the whole hunk by writing all-or-nothing into selectedLines.
  • HunkStagingToolbar: Select all now targets every modifiable line across all hunks; the counter reports X of Y lines.

Cmd+S shortcut + docs (0032212)

  • .keyboardShortcut("s", modifiers: .command) on both Stage selected and Unstage selected buttons in the toolbar. The shortcut fires whichever action is active given the selected file's staged state.
  • README: interactive staging line-level moved to Done, removed from Next, and the pending keyboard-shortcuts bullet trimmed to only mention what's still missing.

Engagement badges (5b5134d)

  • Adds shields.io/github/stars|forks|issues|last-commit badges alongside the existing CI / Platform / Swift / License / Sponsors row.

Not in this PR

  • Discard selected lines (the git checkout -p equivalent). Destructive; deserves its own review.
  • Cmd+Enter commit and command palette — separate follow-up.

Test plan

  • Repo with a file containing two modified regions → Changes tab → pick ONE + line in one hunk → Cmd+Sgit diff --cached shows only that line; git diff still shows the rest
  • In a hunk that mixes - and +, select only the + → stage → the - does NOT land in the index (demoted to context) and the file patches cleanly
  • @@ header checkbox still selects every modifiable line in that hunk; toolbar Select all selects every line across all hunks
  • Staged file view → pick lines → Cmd+S → those lines leave the index, rest stays staged
  • Untracked file → no toolbar, no checkboxes
  • Conflicted file → still shows ConflictView, no staging toolbar
  • History tab commit diff → read-only, no checkboxes
  • xcodebuild clean, swiftlint --strict clean

Stage 4 of the interactive-staging plan. Replaces the whole-hunk-only
selection model with per-line selection, falling back to whole-hunk
behaviour when every modifiable line in a hunk is picked.

- AppState.selectedHunks (Set<Int>) becomes selectedLines
  ([Int: Set<Int>]) keyed by hunk index, valued by line indices within
  that hunk's lines array. One model covers both hunk-whole and
  partial selections.
- DiffFile.patchText(forLines:) walks each selected hunk and rewrites
  the body: unselected + lines are dropped, unselected - lines are
  demoted to context so the line survives the partial patch, and the
  hunk header's oldCount / newCount are recomputed. oldStart /
  newStart stay anchored to their original positions.
- GitCoordinator.stageSelectedLines / unstageSelectedLines replace
  the previous hunk-only helpers and pipe the rewritten patch to
  git apply --cached (with --reverse for unstaging).
- DiffView: every + / - DiffLineView gains an optional selection
  binding and a checkbox. Context rows reserve the slot so columns
  stay aligned. The @@ header checkbox still toggles the whole hunk
  by writing all-or-nothing into selectedLines.
- HunkStagingToolbar: Select all now targets every modifiable line
  across all hunks; the counter reports "X of Y lines" and the
  Stage / Unstage button flips based on the selected file's isStaged.
Adds four shields.io badges next to the existing CI / Platform /
Swift / License / Sponsors row so the repo page surfaces engagement
signals (stargazers, forks, open issues) and freshness (last commit)
at a glance.
Binds Cmd+S to the visible Stage / Unstage selected button in the
changes diff toolbar. The shortcut fires whichever action is active
given the selected file's staged state.

Also promotes interactive staging — line level to Done in the README
roadmap, removes it from Next, and trims Cmd+S from the pending
keyboard-shortcuts bullet since it ships here.
@poolcamacho poolcamacho merged commit 957a733 into master Apr 20, 2026
3 checks passed
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.

1 participant