forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPostProcessingSuggestionServiceMiddleware.swift
More file actions
128 lines (113 loc) · 5.04 KB
/
Copy pathPostProcessingSuggestionServiceMiddleware.swift
File metadata and controls
128 lines (113 loc) · 5.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import Foundation
import SuggestionBasic
public struct PostProcessingSuggestionServiceMiddleware: SuggestionServiceMiddleware {
public init() {}
public func getSuggestion(
_ request: SuggestionRequest,
configuration: SuggestionServiceConfiguration,
next: Next
) async -> AsyncThrowingStream<[CodeSuggestion], Error> {
return await next(request).handled(
handleCodeSuggestions: {
$0.compactMap { suggestion in
var suggestion = suggestion
if suggestion.isActionOnly {
return suggestion
}
if suggestion.text
.allSatisfy({ $0.isWhitespace || $0.isNewline }) { return nil }
Self.removeTrailingWhitespacesAndNewlines(&suggestion)
Self.removeRedundantClosingParenthesis(&suggestion, lines: request.lines)
if !Self
.checkIfSuggestionHasNoEffect(suggestion, request: request) { return nil }
Self.injectReplacingLines(&suggestion, request: request)
return suggestion
}
}
)
}
static func removeTrailingWhitespacesAndNewlines(_ suggestion: inout CodeSuggestion) {
suggestion.text = suggestion.text.removedTrailingWhitespacesAndNewlines()
}
static func injectReplacingLines(
_ suggestion: inout CodeSuggestion,
request: SuggestionRequest
) {
guard !request.lines.isEmpty else { return }
let range = suggestion.range
let lowerBound = max(0, range.start.line)
let upperBound = max(lowerBound, min(request.lines.count - 1, range.end.line))
suggestion.replacingLines = Array(request.lines[lowerBound...upperBound])
}
/// Remove the parenthesis in the last line of the suggestion if
/// - It contains only closing parenthesis
/// - It's identical to the next line below the range of the suggestion
static func removeRedundantClosingParenthesis(
_ suggestion: inout CodeSuggestion,
lines: [String]
) {
let nextLineIndex = suggestion.range.end.line + 1
guard nextLineIndex < lines.endIndex, nextLineIndex >= 0 else { return }
let nextLine = lines[nextLineIndex].dropLast(1)
let lineBreakIndex = suggestion.text.lastIndex(where: { $0.isNewline })
let lastLineIndex = if let index = lineBreakIndex {
suggestion.text.index(after: index)
} else {
suggestion.text.startIndex
}
guard lastLineIndex < suggestion.text.endIndex else { return }
let lastSuggestionLine = suggestion.text[lastLineIndex...]
guard lastSuggestionLine == nextLine else { return }
let closingParenthesis: [Character] = [")", "]", "}", ">"]
let validCharacters = Set(closingParenthesis + [" ", ","])
let trimmedLastSuggestionLine = nextLine.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmedLastSuggestionLine.isEmpty else { return }
if trimmedLastSuggestionLine == "```"
|| trimmedLastSuggestionLine == "\"\"\""
|| trimmedLastSuggestionLine.allSatisfy({ validCharacters.contains($0) })
{
if let lastIndex = lineBreakIndex {
suggestion.text = String(suggestion.text[..<lastIndex])
} else {
suggestion.text = ""
}
suggestion.middlewareComments.append("Removed redundant closing parenthesis.")
}
}
static func checkIfSuggestionHasNoEffect(
_ suggestion: CodeSuggestion,
request: SuggestionRequest
) -> Bool {
// We only check suggestions that are on a single line.
if suggestion.range.isOneLine {
let line = suggestion.range.start.line
if line >= 0, line < request.lines.count {
let replacingText = request.lines[line]
let start = suggestion.range.start.character
let end = suggestion.range.end.character
if let endIndex = replacingText.utf16.index(
replacingText.startIndex,
offsetBy: end,
limitedBy: replacingText.endIndex
),
let startIndex = replacingText.utf16.index(
replacingText.startIndex,
offsetBy: start,
limitedBy: endIndex
),
startIndex < endIndex
{
let replacingRange = startIndex..<endIndex
// Build up the replaced text.
let replacedText = replacingText.replacingCharacters(
in: replacingRange,
with: suggestion.text
)
// If it's identical to the original text, ignore the suggestion.
if replacedText == replacingText { return false }
}
}
}
return true
}
}