Skip to content

Commit 5709e1a

Browse files
committed
Add a SourceKit plugin to handle code completion requests
This adds a sourcekitd plugin that drives the code completion requests. It also includes a `CompletionScoring` module that’s used to rank code completion results based on their contextual match, allowing us to show more relevant code completion results at the top.
1 parent 233f2e6 commit 5709e1a

File tree

125 files changed

+21424
-89
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

125 files changed

+21424
-89
lines changed

Package.swift

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ var products: [Product] = [
1313
.executable(name: "sourcekit-lsp", targets: ["sourcekit-lsp"]),
1414
.library(name: "_SourceKitLSP", targets: ["SourceKitLSP"]),
1515
.library(name: "LSPBindings", targets: ["LanguageServerProtocol", "LanguageServerProtocolJSONRPC"]),
16+
.library(name: "SwiftSourceKitPlugin", type: .dynamic, targets: ["SwiftSourceKitPlugin"]),
17+
.library(name: "SwiftSourceKitClientPlugin", type: .dynamic, targets: ["SwiftSourceKitClientPlugin"]),
1618
]
1719

1820
var targets: [Target] = [
@@ -109,6 +111,40 @@ var targets: [Target] = [
109111
dependencies: []
110112
),
111113

114+
// MARK: CompletionScoring
115+
116+
.target(
117+
name: "CompletionScoring",
118+
dependencies: [],
119+
swiftSettings: globalSwiftSettings
120+
),
121+
122+
.target(
123+
name: "CompletionScoringForPlugin",
124+
dependencies: [],
125+
swiftSettings: globalSwiftSettings
126+
),
127+
128+
.testTarget(
129+
name: "CompletionScoringTests",
130+
dependencies: ["CompletionScoring", "CompletionScoringTestSupport", "SwiftExtensions"],
131+
swiftSettings: globalSwiftSettings
132+
),
133+
134+
.testTarget(
135+
name: "CompletionScoringPerfTests",
136+
dependencies: ["CompletionScoring", "CompletionScoringTestSupport", "SwiftExtensions"],
137+
swiftSettings: globalSwiftSettings
138+
),
139+
140+
// MARK: CompletionScoringTestSupport
141+
142+
.target(
143+
name: "CompletionScoringTestSupport",
144+
dependencies: ["CompletionScoring", "SwiftExtensions"],
145+
swiftSettings: globalSwiftSettings
146+
),
147+
112148
// MARK: CSKTestSupport
113149

114150
.target(
@@ -273,6 +309,21 @@ var targets: [Target] = [
273309
swiftSettings: globalSwiftSettings + lspLoggingSwiftSettings
274310
),
275311

312+
.target(
313+
name: "SKLoggingForPlugin",
314+
dependencies: [
315+
"SwiftExtensionsForPlugin"
316+
],
317+
exclude: ["CMakeLists.txt"],
318+
swiftSettings: globalSwiftSettings + lspLoggingSwiftSettings + [
319+
// We can't depend on swift-crypto in the plugin because we can't module-alias it due to https://github.com/swiftlang/swift-package-manager/issues/8119
320+
.define("NO_CRYPTO_DEPENDENCY"),
321+
.unsafeFlags([
322+
"-module-alias", "SwiftExtensions=SwiftExtensionsForPlugin",
323+
]),
324+
]
325+
),
326+
276327
.testTarget(
277328
name: "SKLoggingTests",
278329
dependencies: [
@@ -308,6 +359,21 @@ var targets: [Target] = [
308359
swiftSettings: globalSwiftSettings
309360
),
310361

362+
.target(
363+
name: "SKUtilitiesForPlugin",
364+
dependencies: [
365+
"SKLoggingForPlugin",
366+
"SwiftExtensionsForPlugin",
367+
],
368+
exclude: ["CMakeLists.txt"],
369+
swiftSettings: globalSwiftSettings + [
370+
.unsafeFlags([
371+
"-module-alias", "SKLogging=SKLoggingForPlugin",
372+
"-module-alias", "SwiftExtensions=SwiftExtensionsForPlugin",
373+
])
374+
]
375+
),
376+
311377
.testTarget(
312378
name: "SKUtilitiesTests",
313379
dependencies: [
@@ -354,6 +420,22 @@ var targets: [Target] = [
354420
swiftSettings: globalSwiftSettings
355421
),
356422

423+
.target(
424+
name: "SourceKitDForPlugin",
425+
dependencies: [
426+
"Csourcekitd",
427+
"SKLoggingForPlugin",
428+
"SwiftExtensionsForPlugin",
429+
],
430+
exclude: ["CMakeLists.txt", "sourcekitd_uids.swift.gyb"],
431+
swiftSettings: globalSwiftSettings + [
432+
.unsafeFlags([
433+
"-module-alias", "SKLogging=SKLoggingForPlugin",
434+
"-module-alias", "SwiftExtensions=SwiftExtensionsForPlugin",
435+
])
436+
]
437+
),
438+
357439
.testTarget(
358440
name: "SourceKitDTests",
359441
dependencies: [
@@ -429,6 +511,13 @@ var targets: [Target] = [
429511
swiftSettings: globalSwiftSettings
430512
),
431513

514+
.target(
515+
name: "SwiftExtensionsForPlugin",
516+
dependencies: ["CAtomics"],
517+
exclude: ["CMakeLists.txt"],
518+
swiftSettings: globalSwiftSettings
519+
),
520+
432521
.testTarget(
433522
name: "SwiftExtensionsTests",
434523
dependencies: [
@@ -439,6 +528,80 @@ var targets: [Target] = [
439528
swiftSettings: globalSwiftSettings
440529
),
441530

531+
// MARK: SwiftSourceKitClientPlugin
532+
533+
.target(
534+
name: "SwiftSourceKitClientPlugin",
535+
dependencies: [
536+
"Csourcekitd",
537+
"SourceKitDForPlugin",
538+
"SwiftSourceKitPluginCommon",
539+
],
540+
swiftSettings: globalSwiftSettings + [
541+
.unsafeFlags([
542+
"-module-alias", "SourceKitD=SourceKitDForPlugin",
543+
])
544+
]
545+
),
546+
547+
// MARK: SwiftSourceKitPluginCommon
548+
549+
.target(
550+
name: "SwiftSourceKitPluginCommon",
551+
dependencies: [
552+
"Csourcekitd",
553+
"SourceKitDForPlugin",
554+
"SwiftExtensionsForPlugin",
555+
"SKLoggingForPlugin",
556+
],
557+
swiftSettings: globalSwiftSettings + [
558+
.unsafeFlags([
559+
"-module-alias", "SourceKitD=SourceKitDForPlugin",
560+
"-module-alias", "SwiftExtensions=SwiftExtensionsForPlugin",
561+
"-module-alias", "SKLogging=SKLoggingForPlugin",
562+
])
563+
]
564+
),
565+
566+
// MARK: SwiftSourceKitPlugin
567+
568+
.target(
569+
name: "SwiftSourceKitPlugin",
570+
dependencies: [
571+
"Csourcekitd",
572+
"CompletionScoringForPlugin",
573+
"SKUtilitiesForPlugin",
574+
"SKLoggingForPlugin",
575+
"SourceKitDForPlugin",
576+
"SwiftSourceKitPluginCommon",
577+
"SwiftExtensionsForPlugin",
578+
],
579+
swiftSettings: globalSwiftSettings + [
580+
.unsafeFlags([
581+
"-module-alias", "CompletionScoring=CompletionScoringForPlugin",
582+
"-module-alias", "SKUtilities=SKUtilitiesForPlugin",
583+
"-module-alias", "SourceKitD=SourceKitDForPlugin",
584+
"-module-alias", "SKLogging=SKLoggingForPlugin",
585+
"-module-alias", "SwiftExtensions=SwiftExtensionsForPlugin",
586+
])
587+
]
588+
),
589+
590+
.testTarget(
591+
name: "SwiftSourceKitPluginTests",
592+
dependencies: [
593+
"BuildSystemIntegration",
594+
"CompletionScoring",
595+
"Csourcekitd",
596+
"LanguageServerProtocol",
597+
"SKTestSupport",
598+
"SourceKitD",
599+
"SwiftExtensions",
600+
"ToolchainRegistry",
601+
],
602+
swiftSettings: globalSwiftSettings
603+
),
604+
442605
// MARK: ToolchainRegistry
443606

444607
.target(
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
/// Represents a composite score formed from a semantic and textual components.
16+
///
17+
/// The textual component forms the bulk of the score typically having a value in the 10's to 100's.
18+
/// You can think of the semantic component as a bonus to the text score.
19+
/// It usually has a value between 0 and 2, and is used as a multiplier.
20+
package struct CompletionScore: Comparable {
21+
package var semanticComponent: Double
22+
package var textComponent: Double
23+
24+
package init(textComponent: Double, semanticComponent: Double) {
25+
self.semanticComponent = semanticComponent
26+
self.textComponent = textComponent
27+
}
28+
29+
package init(textComponent: Double, semanticClassification: SemanticClassification) {
30+
self.semanticComponent = semanticClassification.score
31+
self.textComponent = textComponent
32+
}
33+
34+
package var value: Double {
35+
semanticComponent * textComponent
36+
}
37+
38+
package static func < (_ lhs: Self, _ rhs: Self) -> Bool {
39+
lhs.value < rhs.value
40+
}
41+
}
42+
43+
// MARK: - Deprecated -
44+
extension CompletionScore {
45+
/// There is no natural order to these arguments, so they're alphabetical.
46+
@available(
47+
*,
48+
deprecated,
49+
renamed:
50+
"SemanticClassification(completionKind:deprecationStatus:flair:moduleProximity:popularity:scopeProximity:structuralProximity:synchronicityCompatibility:typeCompatibility:)"
51+
)
52+
package static func semanticScore(
53+
completionKind: CompletionKind,
54+
deprecationStatus: DeprecationStatus,
55+
flair: Flair,
56+
moduleProximity: ModuleProximity,
57+
popularity: Popularity,
58+
scopeProximity: ScopeProximity,
59+
structuralProximity: StructuralProximity,
60+
synchronicityCompatibility: SynchronicityCompatibility,
61+
typeCompatibility: TypeCompatibility
62+
) -> Double {
63+
SemanticClassification(
64+
availability: deprecationStatus,
65+
completionKind: completionKind,
66+
flair: flair,
67+
moduleProximity: moduleProximity,
68+
popularity: popularity,
69+
scopeProximity: scopeProximity,
70+
structuralProximity: structuralProximity,
71+
synchronicityCompatibility: synchronicityCompatibility,
72+
typeCompatibility: typeCompatibility
73+
).score
74+
}
75+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
package enum Availability: Equatable {
16+
/// Example: Either not tagged, or explicit availability is compatible with current build context
17+
case available
18+
19+
/// Example: Explicitly unavailable in current build context - ie, only for another platform.
20+
case unavailable
21+
22+
/// Example: deprecated in the future
23+
case softDeprecated
24+
25+
/// Example: deprecated in the present, or past
26+
case deprecated
27+
28+
/// Completion provider doesn't know if the method is deprecated or not
29+
case unknown
30+
31+
/// Example: keyword
32+
case inapplicable
33+
34+
/// Example: Provider was written before this enum existed, and didn't have an opportunity to provide a value
35+
case unspecified
36+
}
37+
38+
extension Availability: BinaryCodable {
39+
package init(_ decoder: inout BinaryDecoder) throws {
40+
self = try decoder.decodeEnumByte { decoder, n in
41+
switch n {
42+
case 0: return .available
43+
case 1: return .unavailable
44+
case 2: return .softDeprecated
45+
case 3: return .deprecated
46+
case 4: return .unknown
47+
case 5: return .inapplicable
48+
case 6: return .unspecified
49+
default: return nil
50+
}
51+
}
52+
}
53+
54+
package func encode(_ encoder: inout BinaryEncoder) {
55+
let value: UInt8
56+
switch self {
57+
case .available: value = 0
58+
case .unavailable: value = 1
59+
case .softDeprecated: value = 2
60+
case .deprecated: value = 3
61+
case .unknown: value = 4
62+
case .inapplicable: value = 5
63+
case .unspecified: value = 6
64+
}
65+
encoder.write(value)
66+
}
67+
}
68+
69+
@available(*, deprecated, renamed: "Availability")
70+
package typealias DeprecationStatus = Availability
71+
72+
extension Availability {
73+
@available(*, deprecated, renamed: "Availability.available")
74+
package static let none = DeprecationStatus.available
75+
}

0 commit comments

Comments
 (0)