Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ struct AgentContextExportIdentity: Equatable {
let worktreeBindingFingerprint: String
}

struct AgentContextSelectionSummary: Equatable {
let totalExplicitFileCount: Int
let fullFileCount: Int
let slicedFileCount: Int
let sliceRangeCount: Int

var headlineText: String {
let fileText = "\(totalExplicitFileCount) file\(totalExplicitFileCount == 1 ? "" : "s")"
guard slicedFileCount > 0 else { return fileText }
let rangeText = "\(sliceRangeCount) range\(sliceRangeCount == 1 ? "" : "s")"
return "\(fileText) · \(slicedFileCount) sliced · \(rangeText)"
}
}

struct AgentContextExportSourceBuildRequest {
let requestedTabID: UUID?
let activeComposeTabID: UUID?
Expand Down Expand Up @@ -167,22 +181,35 @@ enum AgentContextExportResolver {
let canRemove: Bool
}

static func explicitSelectionFileCount(_ selection: StoredSelection) -> Int {
var seen = Set<String>()
for path in selection.selectedPaths {
seen.insert(normalizedSelectionKey(path))
}
static func selectionSummary(for selection: StoredSelection) -> AgentContextSelectionSummary {
var explicitFileKeys = Set(selection.selectedPaths.map(normalizedSelectionKey))
var slicedFileKeys = Set<String>()
var sliceRangeCount = 0

for (path, ranges) in selection.slices where !ranges.isEmpty {
seen.insert(normalizedSelectionKey(path))
let key = normalizedSelectionKey(path)
explicitFileKeys.insert(key)
slicedFileKeys.insert(key)
sliceRangeCount += ranges.count
}
return seen.count

return AgentContextSelectionSummary(
totalExplicitFileCount: explicitFileKeys.count,
fullFileCount: explicitFileKeys.count - slicedFileKeys.count,
slicedFileCount: slicedFileKeys.count,
sliceRangeCount: sliceRangeCount
)
}

static func explicitSelectionFileCount(_ selection: StoredSelection) -> Int {
selectionSummary(for: selection).totalExplicitFileCount
}

static func displayFileCount(
resolvedModel _: AgentContextExportModel?,
sourceSelection: StoredSelection
) -> Int {
explicitSelectionFileCount(sourceSelection)
selectionSummary(for: sourceSelection).totalExplicitFileCount
}

static func lookupContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ struct AgentContextPill: View {
runtimeVM.snapshot.effectiveContextWindowTokens
}

private var selectionSummary: AgentContextSelectionSummary {
AgentContextExportResolver.selectionSummary(for: currentExportSourceSelection)
}

private var fileCount: Int {
AgentContextExportResolver.displayFileCount(
resolvedModel: nil,
sourceSelection: currentExportSourceSelection
)
selectionSummary.totalExplicitFileCount
}

private var currentExportSourceSelection: StoredSelection {
Expand All @@ -56,7 +57,7 @@ struct AgentContextPill: View {
}

private var fileSummaryText: String {
"\(fileCount) file\(fileCount == 1 ? "" : "s")"
selectionSummary.headlineText
}

private var contextUsageTooltip: String {
Expand Down Expand Up @@ -95,6 +96,7 @@ struct AgentContextPill: View {
.font(fontPreset.swiftUIFont(sizeAtNormal: 12, weight: .medium))
.foregroundStyle(.secondary)
.lineLimit(1)
.fixedSize(horizontal: true, vertical: false)

AgentContextIndicator(
contextWindowTokens: contextWindowTokens,
Expand Down Expand Up @@ -138,7 +140,7 @@ struct AgentContextPill: View {
Text("Selected")
.font(fontPreset.swiftUIFont(sizeAtNormal: 10))
.foregroundStyle(.tertiary)
Text("\(fileCount) files")
Text(fileSummaryText)
.font(fontPreset.swiftUIFont(sizeAtNormal: 12, weight: .semibold))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ struct AgentExportCard: View {
makeExportSource(flushPendingUI: false).exportContextIdentity
}

private var displayFileCount: Int {
AgentContextExportResolver.displayFileCount(
resolvedModel: currentExportModel,
sourceSelection: makeExportSource(flushPendingUI: false).selection
private var selectionSummary: AgentContextSelectionSummary {
AgentContextExportResolver.selectionSummary(
for: makeExportSource(flushPendingUI: false).selection
)
}

Expand Down Expand Up @@ -103,7 +102,8 @@ struct AgentExportCard: View {
// MARK: - Files Button

private var filesButton: some View {
let selectionCount = displayFileCount
let summary = selectionSummary
let selectionCount = summary.totalExplicitFileCount

return Button {
showSelectedFilesPopover.toggle()
Expand All @@ -112,9 +112,10 @@ struct AgentExportCard: View {
Image(systemName: "doc.on.doc")
.font(.system(size: 10, weight: .medium))
.foregroundStyle(.secondary)
Text("\(selectionCount) file\(selectionCount == 1 ? "" : "s")")
Text(summary.headlineText)
.font(.system(size: 10, weight: .medium))
.lineLimit(1)
.multilineTextAlignment(.trailing)
.lineLimit(2)
}
.padding(.horizontal, 8)
.padding(.vertical, 5)
Expand Down
100 changes: 100 additions & 0 deletions Tests/RepoPromptTests/AgentMode/AgentContextExportResolverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,106 @@ final class AgentContextExportResolverTests: XCTestCase {
)
}

func testSelectionSummaryDistinguishesFullAndSlicedFiles() {
let selection = StoredSelection(
selectedPaths: ["Sources/Full.swift", "Sources/Sliced.swift"],
slices: [
"Sources/Sliced.swift": [
LineRange(start: 2, end: 4),
LineRange(start: 8, end: 10)
]
],
codemapAutoEnabled: false
)

let summary = AgentContextExportResolver.selectionSummary(for: selection)

XCTAssertEqual(summary.totalExplicitFileCount, 2)
XCTAssertEqual(summary.fullFileCount, 1)
XCTAssertEqual(summary.slicedFileCount, 1)
XCTAssertEqual(summary.sliceRangeCount, 2)
XCTAssertEqual(summary.headlineText, "2 files · 1 sliced · 2 ranges")
}

func testSelectionSummaryIncludesLegacySliceOnlyKey() {
let selection = StoredSelection(
slices: ["Sources/SliceOnly.swift": [LineRange(start: 3, end: 7)]],
codemapAutoEnabled: false
)

let summary = AgentContextExportResolver.selectionSummary(for: selection)

XCTAssertEqual(summary.totalExplicitFileCount, 1)
XCTAssertEqual(summary.fullFileCount, 0)
XCTAssertEqual(summary.slicedFileCount, 1)
XCTAssertEqual(summary.sliceRangeCount, 1)
XCTAssertEqual(summary.headlineText, "1 file · 1 sliced · 1 range")
}

func testSelectionSummaryDeduplicatesSelectedPathWithSlices() {
let selection = StoredSelection(
selectedPaths: ["Sources/App.swift"],
slices: ["Sources/App.swift": [LineRange(start: 1, end: 2)]],
codemapAutoEnabled: false
)

let summary = AgentContextExportResolver.selectionSummary(for: selection)

XCTAssertEqual(summary.totalExplicitFileCount, 1)
XCTAssertEqual(summary.fullFileCount, 0)
XCTAssertEqual(summary.slicedFileCount, 1)
XCTAssertEqual(summary.sliceRangeCount, 1)
}

func testSelectionSummaryExcludesEmptySlicesAndAutoCodemaps() {
let selection = StoredSelection(
autoCodemapPaths: ["Sources/Dependency.swift"],
slices: ["Sources/Empty.swift": []],
codemapAutoEnabled: true
)

let summary = AgentContextExportResolver.selectionSummary(for: selection)

XCTAssertEqual(summary.totalExplicitFileCount, 0)
XCTAssertEqual(summary.fullFileCount, 0)
XCTAssertEqual(summary.slicedFileCount, 0)
XCTAssertEqual(summary.sliceRangeCount, 0)
XCTAssertEqual(summary.headlineText, "0 files")
}

func testSelectionSummaryRetainsFullOnlyFormatting() {
let singular = AgentContextExportResolver.selectionSummary(
for: StoredSelection(selectedPaths: ["One.swift"], codemapAutoEnabled: false)
)
let plural = AgentContextExportResolver.selectionSummary(
for: StoredSelection(selectedPaths: ["One.swift", "Two.swift"], codemapAutoEnabled: false)
)

XCTAssertEqual(singular.headlineText, "1 file")
XCTAssertEqual(plural.headlineText, "2 files")
}

func testSelectionSummaryDeduplicatesNormalizedAliasesAndSumsStoredRanges() {
let selection = StoredSelection(
slices: [
"Sources/Alias.swift": [LineRange(start: 1, end: 2)],
" Sources/Alias.swift ": [
LineRange(start: 4, end: 5),
LineRange(start: 8, end: 9)
]
],
codemapAutoEnabled: false
)

let summary = AgentContextExportResolver.selectionSummary(for: selection)

XCTAssertEqual(summary.totalExplicitFileCount, 1)
XCTAssertEqual(summary.fullFileCount, 0)
XCTAssertEqual(summary.slicedFileCount, 1)
XCTAssertEqual(summary.sliceRangeCount, 3)
XCTAssertEqual(summary.headlineText, "1 file · 1 sliced · 3 ranges")
}

func testAutoCodemapExportResolutionBatchesPopoverPathLookups() async throws {
#if DEBUG
let root = try makeTemporaryRoot(name: "AgentExportAutoCodemapBatch")
Expand Down
Loading