gitbay/Repos/Definition.swift
45 lines · 1938 bytes
1import Foundation
2import SwiftUI
3
4/// Where a name a file uses is defined, from `repo definitions`
5/// (krz/gitbay#333): the first definition, and how many there are. A name defined
6/// once links to its line; one defined more often to a search.
7nonisolated struct Definition: Decodable, Sendable, Hashable {
8 let name: String
9 let count: Int
10 let path: String
11 let line: Int
12
13 private static let scheme = "gitbay-definition"
14 /// The index's names, as the server matches them.
15 nonisolated(unsafe) private static let identifier = /[\p{L}_$][\p{L}\p{N}_$]*/
16
17 /// `line` with each defined name marked as a link the file view opens.
18 /// The highlighting stays; names the index does not define stay plain.
19 static func link(_ line: AttributedString, definitions: [String: Definition]) -> AttributedString {
20 guard !definitions.isEmpty else { return line }
21 var out = line
22 let text = String(line.characters)
23 for match in text.matches(of: identifier) {
24 let name = String(match.output)
25 guard definitions[name] != nil,
26 let lower = AttributedString.Index(match.range.lowerBound, within: out),
27 let upper = AttributedString.Index(match.range.upperBound, within: out) else { continue }
28 var components = URLComponents()
29 components.scheme = scheme
30 components.host = "name"
31 components.path = "/" + name
32 out[lower..<upper].link = components.url
33 out[lower..<upper].underlineStyle = .init(pattern: .dot)
34 }
35 return out
36 }
37
38 /// The name a link from `link(_:definitions:)` carries; nil for any
39 /// other URL.
40 static func name(in url: URL) -> String? {
41 guard url.scheme == scheme else { return nil }
42 let path = url.path(percentEncoded: false)
43 return path.hasPrefix("/") ? String(path.dropFirst()) : nil
44 }
45}