-
Couldn't load subscription status.
- Fork 320
Support custom preparationBatchSize defined via SourceKit's options #2294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -38,6 +38,8 @@ struct JSONSchema: Encodable { | |||||
| case additionalProperties | ||||||
| case markdownDescription | ||||||
| case markdownEnumDescriptions | ||||||
| case oneOf | ||||||
| case const | ||||||
| } | ||||||
| var _schema: String? | ||||||
| var id: String? | ||||||
|
|
@@ -59,6 +61,9 @@ struct JSONSchema: Encodable { | |||||
| /// VSCode extension: Markdown formatted descriptions for rich hover for enum values | ||||||
| /// https://github.com/microsoft/vscode-wiki/blob/main/Setting-Descriptions.md | ||||||
| var markdownEnumDescriptions: [String]? | ||||||
|
|
||||||
| var oneOf: [JSONSchema]? | ||||||
| var const: String? | ||||||
|
|
||||||
| func encode(to encoder: any Encoder) throws { | ||||||
| // Manually implement encoding to use `encodeIfPresent` for HeapBox-ed fields | ||||||
|
|
@@ -82,6 +87,10 @@ struct JSONSchema: Encodable { | |||||
| if let markdownEnumDescriptions { | ||||||
| try container.encode(markdownEnumDescriptions, forKey: .markdownEnumDescriptions) | ||||||
| } | ||||||
| if let oneOf = oneOf, !oneOf.isEmpty { | ||||||
| try container.encode(oneOf, forKey: .oneOf) | ||||||
| } | ||||||
| try container.encodeIfPresent(const, forKey: .const) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -126,13 +135,53 @@ struct JSONSchemaBuilder { | |||||
| schema.properties = properties | ||||||
| schema.required = required | ||||||
| case .enum(let enumInfo): | ||||||
| schema.type = "string" | ||||||
| schema.enum = enumInfo.cases.map(\.name) | ||||||
| // Set `markdownEnumDescriptions` for better rendering in VSCode rich hover | ||||||
| // Unlike `description`, `enumDescriptions` field is not a part of JSON Schema spec, | ||||||
| // so we only set `markdownEnumDescriptions` here. | ||||||
| if enumInfo.cases.contains(where: { $0.description != nil }) { | ||||||
| schema.markdownEnumDescriptions = enumInfo.cases.map { $0.description ?? "" } | ||||||
| let hasAssociatedTypes = enumInfo.cases.contains { $0.associatedProperties != nil && !$0.associatedProperties!.isEmpty } | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally think it reads nicer like this because you can just infer that
Suggested change
|
||||||
|
|
||||||
| if hasAssociatedTypes { | ||||||
| let discriminatorFieldName = enumInfo.discriminatorFieldName ?? "type" | ||||||
| var oneOfSchemas: [JSONSchema] = [] | ||||||
|
|
||||||
| for caseInfo in enumInfo.cases { | ||||||
| var caseSchema = JSONSchema() | ||||||
| caseSchema.type = "object" | ||||||
| caseSchema.description = caseInfo.description | ||||||
| caseSchema.markdownDescription = caseInfo.description | ||||||
|
|
||||||
| var caseProperties: [String: JSONSchema] = [:] | ||||||
| var caseRequired: [String] = [discriminatorFieldName] | ||||||
|
|
||||||
| var discriminatorSchema = JSONSchema() | ||||||
| discriminatorSchema.const = caseInfo.name | ||||||
| caseProperties[discriminatorFieldName] = discriminatorSchema | ||||||
|
|
||||||
| if let associatedProperties = caseInfo.associatedProperties { | ||||||
| for property in associatedProperties { | ||||||
| let propertyType = property.type | ||||||
| var propertySchema = try buildJSONSchema(from: propertyType) | ||||||
| propertySchema.description = property.description | ||||||
| propertySchema.markdownDescription = property.description | ||||||
| caseProperties[property.name] = propertySchema | ||||||
| if !propertyType.isOptional { | ||||||
| caseRequired.append(property.name) | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| caseSchema.properties = caseProperties | ||||||
| caseSchema.required = caseRequired | ||||||
| oneOfSchemas.append(caseSchema) | ||||||
| } | ||||||
|
|
||||||
| schema.oneOf = oneOfSchemas | ||||||
| } else { | ||||||
| schema.type = "string" | ||||||
| schema.enum = enumInfo.cases.map(\.name) | ||||||
| // Set `markdownEnumDescriptions` for better rendering in VSCode rich hover | ||||||
| // Unlike `description`, `enumDescriptions` field is not a part of JSON Schema spec, | ||||||
| // so we only set `markdownEnumDescriptions` here. | ||||||
| if enumInfo.cases.contains(where: { $0.description != nil }) { | ||||||
| schema.markdownEnumDescriptions = enumInfo.cases.map { $0.description ?? "" } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| return schema | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -66,14 +66,34 @@ struct OptionDocumentBuilder { | |||||||||
| try appendProperty(property, indentLevel: indentLevel + 1) | ||||||||||
| } | ||||||||||
| case .enum(let schema): | ||||||||||
| for caseInfo in schema.cases { | ||||||||||
| // Add detailed description for each case if available | ||||||||||
| guard let description = caseInfo.description else { | ||||||||||
| continue | ||||||||||
| let hasAssociatedTypes = schema.cases.contains { $0.associatedProperties != nil && !$0.associatedProperties!.isEmpty } | ||||||||||
|
|
||||||||||
| if hasAssociatedTypes { | ||||||||||
| let discriminatorFieldName = schema.discriminatorFieldName ?? "type" | ||||||||||
| doc += "\(indent) - This is a tagged union discriminated by the `\(discriminatorFieldName)` field. Each case has the following structure:\n" | ||||||||||
|
|
||||||||||
| for caseInfo in schema.cases { | ||||||||||
| doc += "\(indent) - `\(discriminatorFieldName): \"\(caseInfo.name)\"`" | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very nitpicky: I generally find that string literals that contain quotes read nicer when used in multi-line string literals because then you don’t need to escape the quotes.
Suggested change
|
||||||||||
| if let description = caseInfo.description { | ||||||||||
| doc += ": " + description.split(separator: "\n").joined(separator: "\n\(indent) ") | ||||||||||
| } | ||||||||||
| doc += "\n" | ||||||||||
|
|
||||||||||
| if let associatedProperties = caseInfo.associatedProperties { | ||||||||||
| for assocProp in associatedProperties { | ||||||||||
| try appendProperty(assocProp, indentLevel: indentLevel + 2) | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } else { | ||||||||||
| for caseInfo in schema.cases { | ||||||||||
| guard let description = caseInfo.description else { | ||||||||||
| continue | ||||||||||
| } | ||||||||||
| doc += "\(indent) - `\(caseInfo.name)`" | ||||||||||
| doc += ": " + description.split(separator: "\n").joined(separator: "\n\(indent) ") | ||||||||||
| doc += "\n" | ||||||||||
| } | ||||||||||
| doc += "\(indent) - `\(caseInfo.name)`" | ||||||||||
| doc += ": " + description.split(separator: "\n").joined(separator: "\n\(indent) ") | ||||||||||
| doc += "\n" | ||||||||||
| } | ||||||||||
| default: break | ||||||||||
| } | ||||||||||
|
|
@@ -100,8 +120,13 @@ struct OptionDocumentBuilder { | |||||||||
| case .struct(let structInfo): | ||||||||||
| return structInfo.name | ||||||||||
| case .enum(let enumInfo): | ||||||||||
| let cases = enumInfo.cases.map { "\"\($0.name)\"" }.joined(separator: "|") | ||||||||||
| return shouldWrap ? "(\(cases))" : cases | ||||||||||
| let hasAssociatedTypes = enumInfo.cases.contains { $0.associatedProperties != nil && !$0.associatedProperties!.isEmpty } | ||||||||||
| if hasAssociatedTypes { | ||||||||||
| return "object" | ||||||||||
| } else { | ||||||||||
| let cases = enumInfo.cases.map { "\"\($0.name)\"" }.joined(separator: "|") | ||||||||||
| return shouldWrap ? "(\(cases))" : cases | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the Swift.org open source project | ||
| // | ||
| // Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors | ||
| // Licensed under Apache License v2.0 with Runtime Library Exception | ||
| // | ||
| // See https://swift.org/LICENSE.txt for license information | ||
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| /// Defines the batch size for target preparation. | ||
| /// | ||
| /// If nil, SourceKit-LSP will default to preparing 1 target at a time. | ||
| /// | ||
| /// - discriminator: strategy | ||
| public enum PreparationBatchingStrategy: Sendable, Equatable { | ||
| /// Prepare a fixed number of targets in a single batch. | ||
| /// | ||
| /// `batchSize`: The number of targets to prepare in each batch. | ||
| case target(batchSize: Int) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| extension PreparationBatchingStrategy: Codable { | ||
| private enum CodingKeys: String, CodingKey { | ||
| case strategy | ||
| case batchSize | ||
| } | ||
|
|
||
| private enum StrategyValue: String, Codable { | ||
| case target | ||
| } | ||
|
|
||
| public init(from decoder: Decoder) throws { | ||
| let container = try decoder.container(keyedBy: CodingKeys.self) | ||
| let strategy = try container.decode(StrategyValue.self, forKey: .strategy) | ||
|
|
||
| switch strategy { | ||
| case .target: | ||
| let batchSize = try container.decode(Int.self, forKey: .batchSize) | ||
| self = .target(batchSize: batchSize) | ||
| } | ||
| } | ||
|
|
||
| public func encode(to encoder: Encoder) throws { | ||
| var container = encoder.container(keyedBy: CodingKeys.self) | ||
| switch self { | ||
| case .target(let batchSize): | ||
| try container.encode(StrategyValue.target, forKey: .strategy) | ||
| try container.encode(batchSize, forKey: .batchSize) | ||
| } | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you run swift-format to format the source files, which should also add a trailing newline here. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -441,14 +441,13 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable { | |
| if let buildServerWorkspaceRequestsTimeout { | ||
| return .seconds(buildServerWorkspaceRequestsTimeout) | ||
| } | ||
| // The default value needs to strike a balance: If the build server is slow to respond, we don't want to constantly | ||
| // run into this timeout, which causes somewhat expensive computations because we trigger the `buildTargetsChanged` | ||
| // chain. | ||
| // At the same time, we do want to provide functionality based on fallback settings after some time. | ||
| // 15s seems like it should strike a balance here but there is no data backing this value up. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accidentally removed this comment? |
||
| return .seconds(15) | ||
| } | ||
|
|
||
| /// Defines the batch size for target preparation. | ||
| /// If nil, defaults to preparing 1 target at a time. | ||
| public var preparationBatchingStrategy: PreparationBatchingStrategy? | ||
|
|
||
| public init( | ||
| swiftPM: SwiftPMOptions? = .init(), | ||
| fallbackBuildSystem: FallbackBuildSystemOptions? = .init(), | ||
|
|
@@ -462,6 +461,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable { | |
| generatedFilesPath: String? = nil, | ||
| backgroundIndexing: Bool? = nil, | ||
| backgroundPreparationMode: BackgroundPreparationMode? = nil, | ||
| preparationBatchingStrategy: PreparationBatchingStrategy? = nil, | ||
| cancelTextDocumentRequestsOnEditAndClose: Bool? = nil, | ||
| experimentalFeatures: Set<ExperimentalFeature>? = nil, | ||
| swiftPublishDiagnosticsDebounceDuration: Double? = nil, | ||
|
|
@@ -482,6 +482,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable { | |
| self.defaultWorkspaceType = defaultWorkspaceType | ||
| self.backgroundIndexing = backgroundIndexing | ||
| self.backgroundPreparationMode = backgroundPreparationMode | ||
| self.preparationBatchingStrategy = preparationBatchingStrategy | ||
| self.cancelTextDocumentRequestsOnEditAndClose = cancelTextDocumentRequestsOnEditAndClose | ||
| self.experimentalFeatures = experimentalFeatures | ||
| self.swiftPublishDiagnosticsDebounceDuration = swiftPublishDiagnosticsDebounceDuration | ||
|
|
@@ -545,6 +546,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable { | |
| generatedFilesPath: override?.generatedFilesPath ?? base.generatedFilesPath, | ||
| backgroundIndexing: override?.backgroundIndexing ?? base.backgroundIndexing, | ||
| backgroundPreparationMode: override?.backgroundPreparationMode ?? base.backgroundPreparationMode, | ||
| preparationBatchingStrategy: override?.preparationBatchingStrategy ?? base.preparationBatchingStrategy, | ||
| cancelTextDocumentRequestsOnEditAndClose: override?.cancelTextDocumentRequestsOnEditAndClose | ||
| ?? base.cancelTextDocumentRequestsOnEditAndClose, | ||
| experimentalFeatures: override?.experimentalFeatures ?? base.experimentalFeatures, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.