Skip to content

Commit

Permalink
Incorporate changes made in Helix highlight queries
Browse files Browse the repository at this point in the history
In helix-editor/helix#9586, the Helix editor added highlighting for the
`any` keyword.

However, while reviewing that, I noticed that both `some` and `any`
should be more specific: these keywords are legal as identifiers, but
should not be highlighted as identifiers when they are keywords.

For instance, I can write:
```
let any: any Protocol = AnyImplementation()
```

In this example, only the second `any` should be highlighted.

See discussion on #351
  • Loading branch information
alex-pinkus committed Feb 18, 2024
1 parent bd81fff commit c3fefbd
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 1 deletion.
4 changes: 3 additions & 1 deletion queries/highlights.scm
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@
"override"
"convenience"
"required"
"some"
] @keyword

(opaque_type ["some" @keyword])
(existential_type ["any" @keyword])

[
(getter_specifier)
(setter_specifier)
Expand Down
42 changes: 42 additions & 0 deletions test/highlight/ImagePipelineConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// The MIT License (MIT)
//
// Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).

import Foundation
// ^ @include

extension ImagePipeline {
/// The pipeline configuration.
public struct Configuration: @unchecked Sendable {
/// Image cache used by the pipeline.
public var imageCache: (any ImageCaching)? {
// ^ @keyword
// ^ @type
// This exists simply to ensure we don't init ImageCache.shared if the
// user provides their own instance.
get { isCustomImageCacheProvided ? customImageCache : ImageCache.shared }
set {
customImageCache = newValue
isCustomImageCacheProvided = true
}
}

/// Default implementation uses shared ``ImageDecoderRegistry`` to create
/// a decoder that matches the context.
public var makeImageDecoder: @Sendable (ImageDecodingContext) -> (any ImageDecoding)? = {
// ^ @keyword
// ^ @type
ImageDecoderRegistry.shared.decoder(for: $0)
}

/// Instantiates a pipeline configuration.
///
/// - parameter dataLoader: `DataLoader()` by default.
// NOTE: Surgical change on next line: renamed `dataLoader` to `any` to show it is a contextual keyword
public init(any: any DataLoading = DataLoader()) {
// ^ @parameter
// ^ @keyword
// ^ @type
self.dataLoader = any
}
}
260 changes: 260 additions & 0 deletions test/highlight/LazyImage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
// The MIT License (MIT)
//
// Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).

import Foundation
import Nuke
import SwiftUI
import Combine

public typealias ImageRequest = Nuke.ImageRequest
// ^ @keyword
// // ^ @type

/// A view that asynchronously loads and displays an image.
///
/// ``LazyImage`` is designed to be similar to the native [`AsyncImage`](https://developer.apple.com/documentation/SwiftUI/AsyncImage),
/// but it uses [Nuke](https://github.com/kean/Nuke) for loading images. You
/// can take advantage of all of its features, such as caching, prefetching,
/// task coalescing, smart background decompression, request priorities, and more.
@MainActor
@available(iOS 14.0, tvOS 14.0, watchOS 7.0, macOS 10.16, *)
public struct LazyImage<Content: View>: View {
@StateObject private var viewModel = FetchImage()

private var context: LazyImageContext?
private var makeContent: ((LazyImageState) -> Content)?
private var transaction: Transaction
private var pipeline: ImagePipeline = .shared
private var onStart: ((ImageTask) -> Void)?
private var onDisappearBehavior: DisappearBehavior? = .cancel
private var onCompletion: ((Result<ImageResponse, Error>) -> Void)?

// MARK: Initializers

/// Loads and displays an image using `SwiftUI.Image`.
///
/// - Parameters:
/// - url: The image URL.
public init(url: URL?) where Content == Image {
// ^ @parameter
// ^ @type
self.init(request: url.map { ImageRequest(url: $0) })
}

/// Loads and displays an image using `SwiftUI.Image`.
///
/// - Parameters:
/// - request: The image request.
public init(request: ImageRequest?) where Content == Image {
self.context = request.map(LazyImageContext.init)
self.transaction = Transaction(animation: nil)
}

/// Loads an images and displays custom content for each state.
///
/// See also ``init(request:transaction:content:)``
public init(url: URL?,
transaction: Transaction = Transaction(animation: nil),
@ViewBuilder content: @escaping (LazyImageState) -> Content) {
self.init(request: url.map { ImageRequest(url: $0) }, transaction: transaction, content: content)
}

/// Loads an images and displays custom content for each state.
///
/// - Parameters:
/// - request: The image request.
/// - content: The view to show for each of the image loading states.
///
/// ```swift
/// LazyImage(request: $0) { state in
/// if let image = state.image {
/// image // Displays the loaded image.
/// } else if state.error != nil {
/// Color.red // Indicates an error.
/// } else {
/// Color.blue // Acts as a placeholder.
/// }
/// }
/// ```
public init(request: ImageRequest?,
transaction: Transaction = Transaction(animation: nil),
@ViewBuilder content: @escaping (LazyImageState) -> Content) {
self.context = request.map { LazyImageContext(request: $0) }
self.transaction = transaction
self.makeContent = content
}

// MARK: Options

/// Sets processors to be applied to the image.
///
/// If you pass an image requests with a non-empty list of processors as
/// a source, your processors will be applied instead.
public func processors(_ processors: [any ImageProcessing]?) -> Self {
map { $0.context?.request.processors = processors ?? [] }
}

/// Sets the priority of the requests.
public func priority(_ priority: ImageRequest.Priority?) -> Self {
map { $0.context?.request.priority = priority ?? .normal }
}

/// Changes the underlying pipeline used for image loading.
public func pipeline(_ pipeline: ImagePipeline) -> Self {
map { $0.pipeline = pipeline }
}

public enum DisappearBehavior {
/// Cancels the current request but keeps the presentation state of
/// the already displayed image.
case cancel
/// Lowers the request's priority to very low
case lowerPriority
}

/// Gets called when the request is started.
public func onStart(_ closure: @escaping (ImageTask) -> Void) -> Self {
map { $0.viewModel.onStart = closure }
}

/// Override the behavior on disappear. By default, the view is reset.
public func onDisappear(_ behavior: DisappearBehavior?) -> Self {
map { $0.onDisappearBehavior = behavior }
}

/// Gets called when the current request is completed.
public func onCompletion(_ closure: @escaping (Result<ImageResponse, Error>) -> Void) -> Self {
map { $0.onCompletion = closure }
}

private func map(_ closure: (inout LazyImage) -> Void) -> Self {
var copy = self
closure(&copy)
return copy
}

// MARK: Body

public var body: some View {
// ^ @keyword
// ^ @type
ZStack {
if let makeContent = makeContent {
makeContent(viewModel)
} else {
makeDefaultContent(for: viewModel)
}
}
.onAppear { onAppear() }
.onDisappear { onDisappear() }
.onChange(of: context) {
viewModel.load($0?.request)
}
}

// NOTE: surgical change on next line: renamed `state` to `some` to test that `some` is a contextual keyword.
@ViewBuilder
private func makeDefaultContent(for some: LazyImageState) -> some View {
// ^ @parameter
// ^ @keyword
// ^ @type
if let image = some.image {
image
} else {
Color(.secondarySystemBackground)
}
}

private func onAppear() {
viewModel.transaction = transaction
viewModel.pipeline = pipeline
viewModel.onStart = onStart
viewModel.onCompletion = onCompletion
viewModel.load(context?.request)
}

private func onDisappear() {
guard let behavior = onDisappearBehavior else { return }
switch behavior {
case .cancel:
viewModel.cancel()
case .lowerPriority:
viewModel.priority = .veryLow
}
}
}

private struct LazyImageContext: Equatable {
var request: ImageRequest

static func == (lhs: LazyImageContext, rhs: LazyImageContext) -> Bool {
let lhs = lhs.request
let rhs = rhs.request
return lhs.preferredImageId == rhs.preferredImageId &&
lhs.priority == rhs.priority &&
lhs.processors == rhs.processors &&
lhs.priority == rhs.priority &&
lhs.options == rhs.options
}
}

#if DEBUG
@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
struct LazyImage_Previews: PreviewProvider {
static var previews: some View {
// ^ @keyword
Group {
LazyImageDemoView()
.previewDisplayName("LazyImage")

LazyImage(url: URL(string: "https://kean.blog/images/pulse/01.png"))
.previewDisplayName("LazyImage (Default)")

AsyncImage(url: URL(string: "https://kean.blog/images/pulse/01.png"))
.previewDisplayName("AsyncImage")
}
}
}

// This demonstrates that the view reacts correctly to the URL changes.
@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
private struct LazyImageDemoView: View {
@State var url = URL(string: "https://kean.blog/images/pulse/01.png")
@State var isBlured = false
@State var imageViewId = UUID()

var body: some View {
VStack {
Spacer()

LazyImage(url: url) { state in
if let image = state.image {
image.resizable().aspectRatio(contentMode: .fit)
}
}
#if os(iOS) || os(tvOS) || os(macOS) || os(visionOS)
.processors(isBlured ? [ImageProcessors.GaussianBlur()] : [])
#endif
.id(imageViewId) // Example of how to implement retry

Spacer()
VStack(alignment: .leading, spacing: 16) {
Button("Change Image") {
if url == URL(string: "https://kean.blog/images/pulse/01.png") {
url = URL(string: "https://kean.blog/images/pulse/02.png")
} else {
url = URL(string: "https://kean.blog/images/pulse/01.png")
}
}
Button("Retry") { imageViewId = UUID() }
Toggle("Apply Blur", isOn: $isBlured)
}
.padding()
#if os(iOS) || os(tvOS) || os(macOS) || os(visionOS)
.background(Material.ultraThick)
#endif
}
}
}
#endif

1 comment on commit c3fefbd

@7ombie
Copy link

@7ombie 7ombie commented on c3fefbd Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I didn't look at this till now. I get distracted easily.

IIUC, it should be simple to copy this fix back into Helix.

Again, IIUC, the rest of the new code is just example Swift code that contains a bunch of realistic test cases (with comments pointing to various constructs (that the parser ignores)). Seems simple enough.

Please sign in to comment.