Skip to content

Commit 05ea34d

Browse files
committed
Ignore suggestions that contain only spaces and line breaks
1 parent d540879 commit 05ea34d

File tree

3 files changed

+204
-67
lines changed

3 files changed

+204
-67
lines changed

Core/Sources/CopilotService/CopilotService.swift

Lines changed: 87 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -19,83 +19,92 @@ public protocol CopilotSuggestionServiceType {
1919
cursorPosition: CursorPosition,
2020
tabSize: Int,
2121
indentSize: Int,
22-
usesTabsForIndentation: Bool
22+
usesTabsForIndentation: Bool,
23+
ignoreSpaceOnlySuggestions: Bool
2324
) async throws -> [CopilotCompletion]
2425
func notifyAccepted(_ completion: CopilotCompletion) async
2526
func notifyRejected(_ completions: [CopilotCompletion]) async
2627
}
2728

29+
protocol CopilotLSP {
30+
func sendRequest<E: CopilotRequestType>(_ endpoint: E) async throws -> E.Response
31+
}
32+
2833
public class CopilotBaseService {
2934
let projectRootURL: URL
35+
var server: CopilotLSP
36+
37+
init(designatedServer: CopilotLSP) {
38+
projectRootURL = URL(fileURLWithPath: "/")
39+
server = designatedServer
40+
}
3041

3142
init(projectRootURL: URL) {
3243
self.projectRootURL = projectRootURL
33-
}
44+
server = {
45+
let supportURL = FileManager.default.urls(
46+
for: .applicationSupportDirectory,
47+
in: .userDomainMask
48+
).first!.appendingPathComponent("com.intii.CopilotForXcode")
49+
if !FileManager.default.fileExists(atPath: supportURL.path) {
50+
try? FileManager.default
51+
.createDirectory(at: supportURL, withIntermediateDirectories: false)
52+
}
53+
let executionParams = {
54+
let nodePath = UserDefaults.shared.string(forKey: SettingsKey.nodePath) ?? ""
55+
return Process.ExecutionParameters(
56+
path: "/usr/bin/env",
57+
arguments: [
58+
nodePath.isEmpty ? "node" : nodePath,
59+
Bundle.main.url(
60+
forResource: "agent",
61+
withExtension: "js",
62+
subdirectory: "copilot/dist"
63+
)!.path,
64+
"--stdio",
65+
],
66+
environment: [
67+
"PATH": "/usr/bin:/usr/local/bin",
68+
],
69+
currentDirectoryURL: supportURL
70+
)
71+
}()
72+
let localServer = LocalProcessServer(executionParameters: executionParams)
73+
localServer.logMessages = false
74+
let server = InitializingServer(server: localServer)
75+
server.notificationHandler = { _, respond in
76+
respond(nil)
77+
}
3478

35-
lazy var server: InitializingServer = {
36-
let supportURL = FileManager.default.urls(
37-
for: .applicationSupportDirectory,
38-
in: .userDomainMask
39-
).first!.appendingPathComponent("com.intii.CopilotForXcode")
40-
if !FileManager.default.fileExists(atPath: supportURL.path) {
41-
try? FileManager.default
42-
.createDirectory(at: supportURL, withIntermediateDirectories: false)
43-
}
44-
let executionParams = {
45-
let nodePath = UserDefaults.shared.string(forKey: SettingsKey.nodePath) ?? ""
46-
return Process.ExecutionParameters(
47-
path: "/usr/bin/env",
48-
arguments: [
49-
nodePath.isEmpty ? "node" : nodePath,
50-
Bundle.main.url(
51-
forResource: "agent",
52-
withExtension: "js",
53-
subdirectory: "copilot/dist"
54-
)!.path,
55-
"--stdio",
56-
],
57-
environment: [
58-
"PATH": "/usr/bin:/usr/local/bin",
59-
],
60-
currentDirectoryURL: supportURL
61-
)
62-
}()
63-
let localServer = LocalProcessServer(executionParameters: executionParams)
64-
localServer.logMessages = false
65-
let server = InitializingServer(server: localServer)
66-
server.notificationHandler = { _, respond in
67-
respond(nil)
68-
}
79+
server.initializeParamsProvider = {
80+
let capabilities = ClientCapabilities(
81+
workspace: nil,
82+
textDocument: nil,
83+
window: nil,
84+
general: nil,
85+
experimental: nil
86+
)
6987

70-
let projectRoot = self.projectRootURL
71-
server.initializeParamsProvider = {
72-
let capabilities = ClientCapabilities(
73-
workspace: nil,
74-
textDocument: nil,
75-
window: nil,
76-
general: nil,
77-
experimental: nil
78-
)
79-
80-
return InitializeParams(
81-
processId: Int(ProcessInfo.processInfo.processIdentifier),
82-
clientInfo: .init(name: "Copilot for Xcode"),
83-
locale: nil,
84-
rootPath: projectRoot.path,
85-
rootUri: projectRoot.path,
86-
initializationOptions: nil,
87-
capabilities: capabilities,
88-
trace: nil,
89-
workspaceFolders: nil
90-
)
91-
}
88+
return InitializeParams(
89+
processId: Int(ProcessInfo.processInfo.processIdentifier),
90+
clientInfo: .init(name: "Copilot for Xcode"),
91+
locale: nil,
92+
rootPath: projectRootURL.path,
93+
rootUri: projectRootURL.path,
94+
initializationOptions: nil,
95+
capabilities: capabilities,
96+
trace: nil,
97+
workspaceFolders: nil
98+
)
99+
}
92100

93-
server.notificationHandler = { _, respond in
94-
respond(nil)
95-
}
101+
server.notificationHandler = { _, respond in
102+
respond(nil)
103+
}
96104

97-
return server
98-
}()
105+
return server
106+
}()
107+
}
99108
}
100109

101110
public final class CopilotAuthService: CopilotBaseService, CopilotAuthServiceType {
@@ -136,14 +145,19 @@ public final class CopilotSuggestionService: CopilotBaseService, CopilotSuggesti
136145
override public init(projectRootURL: URL = URL(fileURLWithPath: "/")) {
137146
super.init(projectRootURL: projectRootURL)
138147
}
148+
149+
override init(designatedServer: CopilotLSP) {
150+
super.init(designatedServer: designatedServer)
151+
}
139152

140153
public func getCompletions(
141154
fileURL: URL,
142155
content: String,
143156
cursorPosition: CursorPosition,
144157
tabSize: Int,
145158
indentSize: Int,
146-
usesTabsForIndentation: Bool
159+
usesTabsForIndentation: Bool,
160+
ignoreSpaceOnlySuggestions: Bool
147161
) async throws -> [CopilotCompletion] {
148162
guard let languageId = languageIdentifierFromFileURL(fileURL) else { return [] }
149163

@@ -173,7 +187,14 @@ public final class CopilotSuggestionService: CopilotBaseService, CopilotSuggesti
173187
relativePath: relativePath,
174188
languageId: languageId,
175189
position: cursorPosition
176-
))).completions
190+
)))
191+
.completions
192+
.filter { completion in
193+
if ignoreSpaceOnlySuggestions {
194+
return !completion.text.allSatisfy { $0.isWhitespace || $0.isNewline }
195+
}
196+
return true
197+
}
177198

178199
return completions
179200
}
@@ -191,7 +212,7 @@ public final class CopilotSuggestionService: CopilotBaseService, CopilotSuggesti
191212
}
192213
}
193214

194-
extension InitializingServer {
215+
extension InitializingServer: CopilotLSP {
195216
func sendRequest<E: CopilotRequestType>(_ endpoint: E) async throws -> E.Response {
196217
try await sendRequest(endpoint.request)
197218
}

Core/Sources/Service/Workspace.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ final class Workspace {
197197
cursorPosition: cursorPosition,
198198
tabSize: tabSize,
199199
indentSize: indentSize,
200-
usesTabsForIndentation: usesTabsForIndentation
200+
usesTabsForIndentation: usesTabsForIndentation,
201+
ignoreSpaceOnlySuggestions: true
201202
)
202203

203204
guard filespace.suggestionSourceSnapshot == snapshot else { return nil }
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import LanguageServerProtocol
2+
import XCTest
3+
4+
@testable import CopilotService
5+
6+
final class FetchSuggestionTests: XCTestCase {
7+
func test_process_sugestions_from_server() async throws {
8+
struct TestServer: CopilotLSP {
9+
func sendRequest<E>(_: E) async throws -> E.Response where E: CopilotRequestType {
10+
return CopilotRequest.GetCompletionsCycling.Response(completions: [
11+
.init(
12+
text: "Hello World\n",
13+
position: .init((0, 0)),
14+
uuid: "uuid",
15+
range: .init(start: .init((0, 0)), end: .init((0, 4))),
16+
displayText: "Hello"
17+
),
18+
.init(
19+
text: " ",
20+
position: .init((0, 0)),
21+
uuid: "uuid",
22+
range: .init(start: .init((0, 0)), end: .init((0, 1))),
23+
displayText: " "
24+
),
25+
.init(
26+
text: " \n",
27+
position: .init((0, 0)),
28+
uuid: "uuid",
29+
range: .init(start: .init((0, 0)), end: .init((0, 2))),
30+
displayText: " \n"
31+
),
32+
]) as! E.Response
33+
}
34+
}
35+
let service = CopilotSuggestionService(designatedServer: TestServer())
36+
let completions = try await service.getCompletions(
37+
fileURL: .init(fileURLWithPath: "/file.swift"),
38+
content: "",
39+
cursorPosition: .outOfScope,
40+
tabSize: 4,
41+
indentSize: 4,
42+
usesTabsForIndentation: false,
43+
ignoreSpaceOnlySuggestions: false
44+
)
45+
XCTAssertEqual(completions.count, 3)
46+
}
47+
48+
func test_ignore_empty_suggestions() async throws {
49+
struct TestServer: CopilotLSP {
50+
func sendRequest<E>(_: E) async throws -> E.Response where E: CopilotRequestType {
51+
return CopilotRequest.GetCompletionsCycling.Response(completions: [
52+
.init(
53+
text: "Hello World\n",
54+
position: .init((0, 0)),
55+
uuid: "uuid",
56+
range: .init(start: .init((0, 0)), end: .init((0, 4))),
57+
displayText: "Hello"
58+
),
59+
.init(
60+
text: " ",
61+
position: .init((0, 0)),
62+
uuid: "uuid",
63+
range: .init(start: .init((0, 0)), end: .init((0, 1))),
64+
displayText: " "
65+
),
66+
.init(
67+
text: " \n",
68+
position: .init((0, 0)),
69+
uuid: "uuid",
70+
range: .init(start: .init((0, 0)), end: .init((0, 2))),
71+
displayText: " \n"
72+
),
73+
]) as! E.Response
74+
}
75+
}
76+
let service = CopilotSuggestionService(designatedServer: TestServer())
77+
let completions = try await service.getCompletions(
78+
fileURL: .init(fileURLWithPath: "/file.swift"),
79+
content: "",
80+
cursorPosition: .outOfScope,
81+
tabSize: 4,
82+
indentSize: 4,
83+
usesTabsForIndentation: false,
84+
ignoreSpaceOnlySuggestions: true
85+
)
86+
XCTAssertEqual(completions.count, 1)
87+
XCTAssertEqual(completions.first?.text, "Hello World\n")
88+
}
89+
90+
func test_if_language_identifier_is_unknown_returns_empty_array_immediately() async throws {
91+
struct Err: Error, LocalizedError {
92+
var errorDescription: String? {
93+
"sendRequest Should not be falled"
94+
}
95+
}
96+
97+
class TestServer: CopilotLSP {
98+
func sendRequest<E>(_: E) async throws -> E.Response where E: CopilotRequestType {
99+
throw Err()
100+
}
101+
}
102+
let testServer = TestServer()
103+
let service = CopilotSuggestionService(designatedServer: testServer)
104+
let completions = try await service.getCompletions(
105+
fileURL: .init(fileURLWithPath: "/"),
106+
content: "",
107+
cursorPosition: .outOfScope,
108+
tabSize: 4,
109+
indentSize: 4,
110+
usesTabsForIndentation: false,
111+
ignoreSpaceOnlySuggestions: false
112+
)
113+
XCTAssertEqual(completions.count, 0)
114+
}
115+
}

0 commit comments

Comments
 (0)