Skip to content

Commit 9afe7c0

Browse files
committed
Move all selection logic into a TS file
1 parent d4123a9 commit 9afe7c0

File tree

5 files changed

+297
-177
lines changed

5 files changed

+297
-177
lines changed

apps/desktop/src/components/v3/FileListItemWrapper.svelte

Lines changed: 50 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
import { draggableChips } from '$lib/dragging/draggable';
66
import { ChangeDropData } from '$lib/dragging/draggables';
77
import { getFilename } from '$lib/files/utils';
8-
import { previousPathBytesFromTreeChange, type TreeChange } from '$lib/hunks/change';
8+
import { type TreeChange } from '$lib/hunks/change';
9+
import { DiffService, type HunkGroup } from '$lib/hunks/diffService.svelte';
910
import {
10-
DiffService,
11-
hunkGroupToKey,
12-
type HunkAssignments,
13-
type HunkGroup
14-
} from '$lib/hunks/diffService.svelte';
15-
import { hunkHeaderEquals, type HunkHeader } from '$lib/hunks/hunk';
16-
import { ChangeSelectionService, type SelectedHunk } from '$lib/selection/changeSelection.svelte';
11+
someAssignedToCurrentGroupSelected,
12+
ChangeSelectionService,
13+
deselectAllForChangeInGroup,
14+
selectAllForChangeInGroup,
15+
allAssignedToCurrentGroupSelected
16+
} from '$lib/selection/changeSelection.svelte';
1717
import { IdSelection } from '$lib/selection/idSelection.svelte';
1818
import { key, type SelectionId } from '$lib/selection/key';
1919
import { TestId } from '$lib/testing/testIds';
@@ -89,9 +89,9 @@
8989
? diffService.hunkAssignments(projectId, changesKeyResult.current)
9090
: undefined;
9191
});
92-
const selection = $derived(changeSelection.getById(change.path));
9392
const selectedChanges = $derived(idSelection.treeChanges(projectId, selectionId));
9493
const isUncommitted = $derived(selectionId?.type === 'worktree');
94+
const selectedFile = $derived(changeSelection.getById(change.path));
9595
9696
const previousTooltipText = $derived(
9797
(change.status.subject as Rename).previousPath
@@ -109,189 +109,64 @@
109109
return undefined;
110110
});
111111
112-
function allAssignedToCurrentGroup(
113-
assignments: HunkAssignments,
114-
selectionId: SelectionId & { type: 'worktree' }
115-
): boolean {
116-
for (const [key, value] of assignments.entries()) {
117-
if (key === hunkGroupToKey(selectionId.group)) continue;
118-
119-
if (value.has(change.path)) return false;
120-
}
121-
122-
return true;
123-
}
124-
125-
function allHunksForPath(assignments: HunkAssignments, except?: HunkGroup): HunkHeader[] {
126-
const headers = [];
127-
128-
for (const [key, value] of assignments.entries()) {
129-
if (except) {
130-
if (key === hunkGroupToKey(except)) continue;
131-
}
132-
133-
const assignments = value.get(change.path);
134-
if (!assignments) continue;
135-
headers.push(...assignments.map((assignment) => assignment.hunkHeader));
136-
}
137-
138-
return headers;
139-
}
140-
141-
function relevantHunkHeaders(
142-
assignments: HunkAssignments,
143-
selectionId: SelectionId & { type: 'worktree' }
144-
): HunkHeader[] {
145-
const stackGroup = assignments.get(hunkGroupToKey(selectionId.group));
146-
if (!stackGroup) return [];
147-
const hunkAssignments = stackGroup.get(change.path);
148-
149-
return hunkAssignments?.map((assignment) => assignment.hunkHeader) ?? [];
150-
}
151-
152112
function onCheck() {
113+
// TODO: Double check that we change partial hunk selections into whole
114+
// hunk selections.
153115
// Currently selection is only implemented for the worktree changes.
154116
if (selectionId.type !== 'worktree') return;
155117
if (!assignments?.current?.data) return;
156118
157-
// TODO: wtf do you do in the case where a whole group is selected, but
158-
// a differently grouped hunk appears due to a disk change????
159-
if (allAssignedToCurrentGroup(assignments.current.data, selectionId)) {
160-
if (selection.current) {
161-
changeSelection.remove(change.path);
162-
} else {
163-
const { path, pathBytes } = change;
164-
changeSelection.upsert({
165-
type: 'full',
166-
path,
167-
pathBytes,
168-
previousPathBytes: previousPathBytesFromTreeChange(change)
169-
});
170-
}
119+
if (
120+
someAssignedToCurrentGroupSelected(
121+
change,
122+
selectionId.group,
123+
assignments.current.data,
124+
selectedFile.current
125+
)
126+
) {
127+
deselectAllForChangeInGroup(
128+
change,
129+
selectionId.group,
130+
assignments.current.data,
131+
selectedFile.current,
132+
changeSelection
133+
);
171134
} else {
172-
// Handle selection/deselection when not all the hunks are assigned
173-
// to the one group.
174-
const relevantHeaders = relevantHunkHeaders(assignments.current.data, selectionId);
175-
const { path, pathBytes } = change;
176-
if (selection.current?.type === 'full') {
177-
// Currently, all the hunks are selected; we want to replace the
178-
// current selection with one that contains all the hunk hunk
179-
// headers for this path _except_ for those in the current group
180-
const hunkHeadersWithoutGroup = allHunksForPath(
181-
assignments.current.data,
182-
selectionId.group
183-
);
184-
185-
changeSelection.upsert({
186-
type: 'partial',
187-
path,
188-
pathBytes,
189-
previousPathBytes: previousPathBytesFromTreeChange(change),
190-
hunks: hunkHeadersWithoutGroup.map((header) => ({ ...header, type: 'full' }))
191-
});
192-
} else if (selection.current?.type === 'partial') {
193-
const selectedHunks = selection.current.hunks;
194-
// Handle selection/deselection when _some_ of the changes are
195-
// selected for committing. We want to deselect if there are any
196-
// hunks associated to the group already selected.
197-
198-
// TODO: Look at optimizing some of these N+1s
199-
const includesRelevantHunks = selectedHunks.some((hunk) =>
200-
relevantHeaders.some((header) => hunkHeaderEquals(header, hunk))
201-
);
202-
203-
if (includesRelevantHunks) {
204-
// We have found some selected hunks that are in the current
205-
// group, so we want to filter them out of the current
206-
// selection.
207-
const selectionWithoutOwnedHunks = selectedHunks.filter(
208-
(hunk) => !relevantHeaders.some((header) => hunkHeaderEquals(header, hunk))
209-
);
210-
if (selectionWithoutOwnedHunks.length === 0) {
211-
// If there are no hunks left in the selection, then we
212-
// can just clear the selection.
213-
changeSelection.remove(path);
214-
} else {
215-
changeSelection.upsert({
216-
type: 'partial',
217-
path,
218-
pathBytes,
219-
previousPathBytes: previousPathBytesFromTreeChange(change),
220-
hunks: selectionWithoutOwnedHunks
221-
});
222-
}
223-
} else {
224-
// We want to add the hunks that are associated with the
225-
// current group to the selection.
226-
const hunks: SelectedHunk[] = relevantHeaders.map((header) => ({
227-
...header,
228-
type: 'full'
229-
}));
230-
const combinedHunks = [...selection.current.hunks, ...hunks];
231-
const allHunks = allHunksForPath(assignments.current.data);
232-
233-
if (combinedHunks.length === allHunks.length) {
234-
// If all the hunks have ended up selected, let's
235-
// replace it with a "full" selection.
236-
changeSelection.upsert({
237-
type: 'full',
238-
path,
239-
pathBytes,
240-
previousPathBytes: previousPathBytesFromTreeChange(change)
241-
});
242-
} else {
243-
changeSelection.upsert({
244-
type: 'partial',
245-
path,
246-
pathBytes,
247-
previousPathBytes: previousPathBytesFromTreeChange(change),
248-
hunks: [...selection.current.hunks, ...hunks]
249-
});
250-
}
251-
}
252-
} else {
253-
// There is no existing selection so we can simply select all
254-
// the hunks belonging to the group
255-
changeSelection.upsert({
256-
type: 'partial',
257-
path,
258-
pathBytes,
259-
previousPathBytes: previousPathBytesFromTreeChange(change),
260-
hunks: relevantHeaders.map((header) => ({ ...header, type: 'full' }))
261-
});
262-
}
135+
selectAllForChangeInGroup(
136+
change,
137+
selectionId.group,
138+
assignments.current.data,
139+
selectedFile.current,
140+
changeSelection
141+
);
263142
}
264143
}
265144
266145
const checkStatus = $derived.by((): 'checked' | 'indeterminate' | 'unchecked' => {
267146
// Currently selection is only implemented for the worktree changes.
268147
if (selectionId.type !== 'worktree') return 'unchecked';
269148
if (!assignments?.current?.data) return 'unchecked';
270-
const currentSelection = selection.current;
271-
if (!currentSelection) return 'unchecked';
272149
273-
if (currentSelection.type === 'full') {
274-
// If the selection type is "full", then we can assume that since
275-
// this is rendered in the first place that it should indeed be
276-
// checked.
150+
if (
151+
allAssignedToCurrentGroupSelected(
152+
change,
153+
selectionId.group,
154+
assignments.current.data,
155+
selectedFile.current
156+
)
157+
) {
277158
return 'checked';
278159
}
279160
280-
const relevantHeaders = relevantHunkHeaders(assignments.current.data, selectionId);
281-
282-
const includesRelevantHunks = currentSelection.hunks.some((hunk) =>
283-
relevantHeaders.some((header) => hunkHeaderEquals(header, hunk))
284-
);
285-
286-
if (includesRelevantHunks) {
287-
const includesAllRelevantHunks = relevantHeaders.every((hunk) =>
288-
currentSelection.hunks.some((header) => hunkHeaderEquals(header, hunk))
289-
);
290-
if (includesAllRelevantHunks) {
291-
return 'checked';
292-
} else {
293-
return 'indeterminate';
294-
}
161+
if (
162+
someAssignedToCurrentGroupSelected(
163+
change,
164+
selectionId.group,
165+
assignments.current.data,
166+
selectedFile.current
167+
)
168+
) {
169+
return 'indeterminate';
295170
}
296171
297172
return 'unchecked';

apps/desktop/src/components/v3/UnifiedDiffView.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,9 @@
243243
// We could have an encoding for hunk-headers that we can then put into
244244
// a hash set.
245245
return hunks.filter((hunk) =>
246-
pathGroup.some((assignedHunk) => hunkHeaderEquals(hunk, assignedHunk.hunkHeader))
246+
pathGroup.some((assignedHunk) =>
247+
assignedHunk?.hunkHeader ? hunkHeaderEquals(hunk, assignedHunk.hunkHeader) : true
248+
)
247249
);
248250
}
249251
</script>

apps/desktop/src/components/v3/WorktreeChanges.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
if (affectedPaths) {
7676
untrack(() => {
7777
changeSelection.retain(affectedPaths);
78+
// TODO: We need to make this consider all groups
7879
idSelection.retain(affectedPaths, { type: 'ungrouped' });
7980
});
8081
}

apps/desktop/src/lib/hunks/hunk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export type HunkAssignment = {
9898
/** The hunk that is being assigned. Together with path_bytes, this identifies the hunk.
9999
* If the file is binary, or too large to load, this will be None and in this case the path name is the only identity.
100100
*/
101-
readonly hunkHeader: HunkHeader;
101+
readonly hunkHeader: HunkHeader | null;
102102
/** The file path of the hunk. Used for display. */
103103
readonly path: string;
104104
/** The file path of the hunk in bytes. Used to correctly communicate to the backed when creating new assignments */

0 commit comments

Comments
 (0)