Commit 54f83256b5

54f83256b552959a29ee327f55728a9cc75070ba

parent: a615251f3a

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-14 06:07 UTC

Definitions: one id for each code line; a full-width mark

The file view's rows are scrolled to by the index they are drawn with,
so a line far down a long file is found before it is drawn. The target
line's mark spans the row, and only that row carries an identifier and
the selected trait. A symbol's identity includes its kind, since two
can share a line.

Ref #28

Layout: unified · split

gitbay/Repos/SymbolSearchViewModel.swift +1 −1
@@ -12,7 +12,7 @@ final class SymbolSearchViewModel {
1212 let kind: String
1313 let path: String
1414 let line: Int
15 var id: String { "\(path):\(line):\(name)" }
15 var id: String { "\(path):\(line):\(kind):\(name)" }
1616 }
1717
1818 /// The kinds `--kind` takes, as the command's help lists them.
gitbay/Views/Repos/FileView.swift +23 −4
@@ -107,12 +107,15 @@ private struct CodeScrollView: View {
107107 ScrollViewReader { proxy in
108108 ScrollView([.horizontal, .vertical]) {
109109 LazyVStack(alignment: .leading, spacing: 0) {
110 // The row's identity is its 0-based index, the one
111 // scrollTo names: a lazy row not yet drawn has no other.
110112 ForEach(lines.indices, id: \.self) { index in
113 let target = index + 1 == line
111114 Text(lines[index].characters.isEmpty ? " " : lines[index])
112115 .fixedSize()
113 .background(index + 1 == line ? Color.gbMark.opacity(0.18) : .clear)
114 .id(index + 1)
115 .accessibilityIdentifier(index + 1 == line ? "file-target-line" : "")
116 .frame(maxWidth: .infinity, alignment: .leading)
117 .background(target ? Color.gbMark.opacity(0.18) : .clear)
118 .modifier(TargetLine(on: target))
116119 }
117120 }
118121 .padding(12)
@@ -125,7 +128,7 @@ private struct CodeScrollView: View {
125128 // A frame for the highlighted lines to exist before the
126129 // jump, or the lazy stack has nothing to scroll to.
127130 await Task.yield()
128 proxy.scrollTo(line, anchor: UnitPoint(x: 0, y: 0.3))
131 proxy.scrollTo(line - 1, anchor: UnitPoint(x: 0, y: 0.3))
129132 }
130133 }
131134 }
@@ -141,6 +144,22 @@ private struct CodeScrollView: View {
141144 }
142145}
143146
147/// The line a definition or a match opened at: named for VoiceOver and
148/// for UI tests, and left alone on every other row.
149private struct TargetLine: ViewModifier {
150 let on: Bool
151
152 func body(content: Content) -> some View {
153 if on {
154 content
155 .accessibilityIdentifier("file-target-line")
156 .accessibilityAddTraits(.isSelected)
157 } else {
158 content
159 }
160 }
161}
162
144163/// A file's lines, each keeping its highlighting. A trailing newline ends
145164/// the last line rather than starting an empty one.
146165nonisolated func codeLines(_ source: NSAttributedString) -> [AttributedString] {
gitbayTests/SymbolTests.swift +1 −1
@@ -31,7 +31,7 @@ struct SymbolSearchTests {
3131 await model.search(" Dispatch ")
3232
3333 #expect(argv(try #require(stub.seen.first).url) == ["repo", "symbols", "krz/gitbay", "Dispatch"])
34 #expect(model.state?.value?.map(\.id) == ["internal/control/control.go:120:Dispatch", "internal/control/test.go:9:DispatchIn"])
34 #expect(model.state?.value?.map(\.id) == ["internal/control/control.go:120:function:Dispatch", "internal/control/test.go:9:function:DispatchIn"])
3535 }
3636
3737 @Test func aKindNarrowsTheSearch() async throws {