| @@ -107,12 +107,15 @@ private struct CodeScrollView: View { |
| 107 | ScrollViewReader { proxy in |
107 | ScrollViewReader { proxy in |
| 108 | ScrollView([.horizontal, .vertical]) { |
108 | ScrollView([.horizontal, .vertical]) { |
| 109 | LazyVStack(alignment: .leading, spacing: 0) { |
109 | 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. |
| 110 | ForEach(lines.indices, id: \.self) { index in |
112 | ForEach(lines.indices, id: \.self) { index in |
| |
113 | let target = index + 1 == line |
| 111 | Text(lines[index].characters.isEmpty ? " " : lines[index]) |
114 | Text(lines[index].characters.isEmpty ? " " : lines[index]) |
| 112 | .fixedSize() |
115 | .fixedSize() |
| 113 | .background(index + 1 == line ? Color.gbMark.opacity(0.18) : .clear) |
116 | .frame(maxWidth: .infinity, alignment: .leading) |
| 114 | .id(index + 1) |
117 | .background(target ? Color.gbMark.opacity(0.18) : .clear) |
| 115 | .accessibilityIdentifier(index + 1 == line ? "file-target-line" : "") |
118 | .modifier(TargetLine(on: target)) |
| 116 | } |
119 | } |
| 117 | } |
120 | } |
| 118 | .padding(12) |
121 | .padding(12) |
| @@ -125,7 +128,7 @@ private struct CodeScrollView: View { |
| 125 | // A frame for the highlighted lines to exist before the |
128 | // A frame for the highlighted lines to exist before the |
| 126 | // jump, or the lazy stack has nothing to scroll to. |
129 | // jump, or the lazy stack has nothing to scroll to. |
| 127 | await Task.yield() |
130 | 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)) |
| 129 | } |
132 | } |
| 130 | } |
133 | } |
| 131 | } |
134 | } |
| @@ -141,6 +144,22 @@ private struct CodeScrollView: View { |
| 141 | } |
144 | } |
| 142 | } |
145 | } |
| 143 | |
146 | |
| |
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. |
| |
149 | private 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 | |
| 144 | /// A file's lines, each keeping its highlighting. A trailing newline ends |
163 | /// A file's lines, each keeping its highlighting. A trailing newline ends |
| 145 | /// the last line rather than starting an empty one. |
164 | /// the last line rather than starting an empty one. |
| 146 | nonisolated func codeLines(_ source: NSAttributedString) -> [AttributedString] { |
165 | nonisolated func codeLines(_ source: NSAttributedString) -> [AttributedString] { |