gitbay/MRs/Suggestion.swift
46 lines · 1893 bytes
3 symbols in this file
1import Foundation
2
3/// The change a review thread's ```suggestion block proposes, as
4/// `mr threads` carries it: lines `startLine` through `endLine` of `path`,
5/// `original` replaced by `replacement`. Both texts end every line with
6/// its terminator, so "" is no lines.
7nonisolated struct Suggestion: Decodable, Sendable, Hashable {
8 let path: String
9 let startLine: Int64
10 let endLine: Int64
11 let original: String
12 let replacement: String
13 /// The lines it replaces have changed at the head, or the file moved;
14 /// it cannot be applied then, and `reason` says which.
15 let outdated: Bool
16 let reason: String?
17 /// "server" where `mr apply-suggestion` commits it; "local" where the
18 /// repository requires signed commits, which only a clone can make.
19 let apply: String
20
21 enum CodingKeys: String, CodingKey {
22 case path, original, replacement, outdated, reason, apply
23 case startLine = "start_line"
24 case endLine = "end_line"
25 }
26
27 var appliesOnServer: Bool { apply == "server" }
28
29 /// The text as lines, without the terminator the last one carries.
30 static func lines(_ text: String) -> [String] {
31 guard !text.isEmpty else { return [] }
32 var lines = text.components(separatedBy: "\n")
33 if lines.last == "" { lines.removeLast() }
34 return lines
35 }
36
37 /// A comment's text without its ```suggestion block, which the thread
38 /// draws as replaced and proposed lines instead.
39 static func withoutBlock(_ body: String) -> String {
40 guard let start = body.range(of: "```suggestion") else { return body }
41 let afterOpen = body[start.upperBound...]
42 guard let close = afterOpen.range(of: "```") else { return body }
43 let rest = body[..<start.lowerBound] + body[close.upperBound...]
44 return rest.trimmingCharacters(in: .whitespacesAndNewlines)
45 }
46}