Skip to content

Async and Faster Folding Calculation #331

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

Open
wants to merge 38 commits into
base: feat/code-folding
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4a73315
Initial Commit - Ribbon View & Toggles
thecoolwinter May 8, 2025
91ddcec
Correct Gutter Padding With Transparency, Dark Mode
thecoolwinter May 8, 2025
0a7519e
Revert Debugging Changes
thecoolwinter May 8, 2025
c056e4a
Add Demo Line Fold Provider (For Testing)
thecoolwinter May 8, 2025
0b840f6
Remove Some Unnecessary Hovering Stuff
thecoolwinter May 8, 2025
5c813dd
Implement Hover State and Animation
thecoolwinter May 8, 2025
8ee94b5
Move DrawingContext Struct
thecoolwinter May 8, 2025
4d9d1d0
Update View When Folds Change
thecoolwinter May 8, 2025
2c1af46
Dispatch Folding Calculation To Background
thecoolwinter May 8, 2025
2f1fdad
Use Lock For Cache, Skip Depth Changes
thecoolwinter May 9, 2025
f8433f3
Sanity Check Range
thecoolwinter May 9, 2025
d618ace
Toggle Folding State
thecoolwinter May 9, 2025
4408fac
Merge branch 'main' into code-folding/ribbon-view
thecoolwinter May 28, 2025
6cbf5e0
Merge branch 'code-folding/ribbon-view' into code-folding/better-fold…
thecoolwinter May 28, 2025
d3c03cf
Make StyledRangeStore Generalized and `Sendable`
thecoolwinter May 28, 2025
c7d7823
Rename to `RangeStore`
thecoolwinter May 28, 2025
26d2b66
Finish Rename
thecoolwinter May 28, 2025
7e7172c
Fix Doc Comment Drawing
thecoolwinter May 28, 2025
1687258
Update Test Names
thecoolwinter May 28, 2025
0acd458
Loosen `RangeStoreElement` Requirements
thecoolwinter May 28, 2025
886c7fe
Merge branch 'feat/code-folding' into code-folding/better-folding-cal…
thecoolwinter May 29, 2025
80f534e
Update Package.resolved
thecoolwinter May 29, 2025
d0c2451
Merge branch 'main' into code-folding/better-folding-calculation
thecoolwinter May 29, 2025
cd5b7c8
Remove Moved Files
thecoolwinter May 29, 2025
b1511ee
Merge branch 'make-styled-range-store-generic' into code-folding/bett…
thecoolwinter May 29, 2025
24d9b7a
Begin Transition To `RangeStore` Model
thecoolwinter May 30, 2025
45ccd75
Back to where we started! (working)
thecoolwinter May 30, 2025
7f6b7b5
Added product icons to example app (#329)
austincondiff Jun 2, 2025
0c6fe3d
Merge branch 'main' into code-folding/better-folding-calculation
thecoolwinter Jun 2, 2025
3b13a30
Move to Swift Concurrency with Async Streams
thecoolwinter Jun 3, 2025
8abf180
Remove some comments
thecoolwinter Jun 3, 2025
8976911
Fix Drawing Ordering, Use Attachment Ranges
thecoolwinter Jun 3, 2025
bab4c8e
Add Straight Line When Adjacent
thecoolwinter Jun 3, 2025
2a952fe
Merge branch 'feat/code-folding' into code-folding/better-folding-cal…
thecoolwinter Jun 3, 2025
aa89ce2
Clean Up, Add Tests
thecoolwinter Jun 3, 2025
fb70a58
Fix Lint Errors, Clean Up Calculator
thecoolwinter Jun 3, 2025
78cb70a
Document the async calculator
thecoolwinter Jun 3, 2025
b5092fe
Merge branch 'CodeEditApp:main' into code-folding/better-folding-calc…
thecoolwinter Jun 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,58 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
"filename" : "CodeEditSourceEditor-Icon-16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
"filename" : "CodeEditSourceEditor-Icon-32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
"filename" : "CodeEditSourceEditor-Icon-32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
"filename" : "CodeEditSourceEditor-Icon-64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
"filename" : "CodeEditSourceEditor-Icon-128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
"filename" : "CodeEditSourceEditor-Icon-256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
"filename" : "CodeEditSourceEditor-Icon-256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
"filename" : "CodeEditSourceEditor-Icon-512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
"filename" : "CodeEditSourceEditor-Icon-512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
"filename" : "CodeEditSourceEditor-Icon-1024.png",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
"version" : 1,
"author" : "xcode"
}
}
}
9 changes: 0 additions & 9 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ let package = Package(
dependencies: [
// A fast, efficient, text view for code.
.package(
url: "https://github.com/CodeEditApp/CodeEditTextView.git",
from: "0.11.1"
path: "../CodeEditTextView"
// url: "https://github.com/CodeEditApp/CodeEditTextView.git",
// from: "0.11.1"
),
// tree-sitter languages
.package(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension TextViewController {
font: font.rulerFont,
textColor: theme.text.color.withAlphaComponent(0.35),
selectedTextColor: theme.text.color,
textView: textView,
controller: self,
delegate: self
)
gutterView.updateWidthIfNeeded()
Expand Down Expand Up @@ -143,6 +143,7 @@ extension TextViewController {
- (self?.scrollView.contentInsets.top ?? 0)

self?.gutterView.needsDisplay = true
self?.gutterView.foldingRibbon.needsDisplay = true
self?.guideView?.updatePosition(in: textView)
self?.scrollView.needsLayout = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,11 @@ extension DispatchQueue {
/// executed if not already on the main thread.
/// - Parameter item: The work item to execute.
/// - Returns: The value of the work item.
static func syncMainIfNot<T>(_ item: @escaping () -> T) -> T {
static func waitMainIfNot<T>(_ item: () -> T) -> T {
if Thread.isMainThread {
return item()
} else {
return DispatchQueue.main.sync {
return item()
}
return DispatchQueue.main.asyncAndWait(execute: item)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// NSBezierPath+RoundedCorners.swift
// CodeEditSourceEditor
//
// Created by Khan Winter on 6/3/25.
//

import AppKit

// Wonderful NSBezierPath extension taken with modification from the playground code at:
// https://github.com/janheiermann/BezierPath-Corners

extension NSBezierPath {
struct Corners: OptionSet {
public let rawValue: Int

public init(rawValue: Corners.RawValue) {
self.rawValue = rawValue
}

public static let topLeft = Corners(rawValue: 1 << 0)
public static let bottomLeft = Corners(rawValue: 1 << 1)
public static let topRight = Corners(rawValue: 1 << 2)
public static let bottomRight = Corners(rawValue: 1 << 3)
}

// swiftlint:disable:next function_body_length
convenience init(rect: CGRect, roundedCorners corners: Corners, cornerRadius: CGFloat) {
self.init()

let maxX = rect.maxX
let minX = rect.minX
let maxY = rect.maxY
let minY = rect.minY
let radius = min(cornerRadius, min(rect.width, rect.height) / 2)

// Start at bottom-left corner
move(to: CGPoint(x: minX + (corners.contains(.bottomLeft) ? radius : 0), y: minY))

// Bottom edge
if corners.contains(.bottomRight) {
line(to: CGPoint(x: maxX - radius, y: minY))
appendArc(
withCenter: CGPoint(x: maxX - radius, y: minY + radius),
radius: radius,
startAngle: 270,
endAngle: 0,
clockwise: false
)
} else {
line(to: CGPoint(x: maxX, y: minY))
}

// Right edge
if corners.contains(.topRight) {
line(to: CGPoint(x: maxX, y: maxY - radius))
appendArc(
withCenter: CGPoint(x: maxX - radius, y: maxY - radius),
radius: radius,
startAngle: 0,
endAngle: 90,
clockwise: false
)
} else {
line(to: CGPoint(x: maxX, y: maxY))
}

// Top edge
if corners.contains(.topLeft) {
line(to: CGPoint(x: minX + radius, y: maxY))
appendArc(
withCenter: CGPoint(x: minX + radius, y: maxY - radius),
radius: radius,
startAngle: 90,
endAngle: 180,
clockwise: false
)
} else {
line(to: CGPoint(x: minX, y: maxY))
}

// Left edge
if corners.contains(.bottomLeft) {
line(to: CGPoint(x: minX, y: minY + radius))
appendArc(
withCenter: CGPoint(x: minX + radius, y: minY + radius),
radius: radius,
startAngle: 180,
endAngle: 270,
clockwise: false
)
} else {
line(to: CGPoint(x: minX, y: minY))
}

close()
}

convenience init(roundingRect: CGRect, capTop: Bool, capBottom: Bool, cornerRadius radius: CGFloat) {
switch (capTop, capBottom) {
case (true, true):
self.init(rect: roundingRect)
case (false, true):
self.init(rect: roundingRect, roundedCorners: [.bottomLeft, .bottomRight], cornerRadius: radius)
case (true, false):
self.init(rect: roundingRect, roundedCorners: [.topLeft, .topRight], cornerRadius: radius)
case (false, false):
self.init(roundedRect: roundingRect, xRadius: radius, yRadius: radius)
}
}
}
19 changes: 19 additions & 0 deletions Sources/CodeEditSourceEditor/Extensions/NSString+TextStory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// NSString+TextStory.swift
// CodeEditSourceEditor
//
// Created by Khan Winter on 6/3/25.
//

import AppKit
import TextStory

extension NSString: @retroactive TextStoring {
public func substring(from range: NSRange) -> String? {
self.substring(with: range)
}

public func applyMutation(_ mutation: TextMutation) {
self.replacingCharacters(in: mutation.range, with: mutation.string)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ extension TextView {
let range = NSRange(location..<end)
return self?.textStorage.substring(from: range)?.data(using: String.nativeUTF16Encoding)
}
return DispatchQueue.syncMainIfNot(workItem)
return DispatchQueue.waitMainIfNot(workItem)
}
}
/// Creates a block for safely reading data for a text provider.
Expand All @@ -45,7 +45,7 @@ extension TextView {
let workItem: () -> String? = {
self?.textStorage.substring(from: range)
}
return DispatchQueue.syncMainIfNot(workItem)
return DispatchQueue.waitMainIfNot(workItem)
}
}
}
8 changes: 4 additions & 4 deletions Sources/CodeEditSourceEditor/Gutter/GutterView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public class GutterView: NSView {
}

/// The view that draws the fold decoration in the gutter.
private var foldingRibbon: FoldingRibbonView
var foldingRibbon: FoldingRibbonView

/// Syntax helper for determining the required space for the folding ribbon.
private var foldingRibbonWidth: CGFloat {
Expand Down Expand Up @@ -137,16 +137,16 @@ public class GutterView: NSView {
font: NSFont,
textColor: NSColor,
selectedTextColor: NSColor?,
textView: TextView,
controller: TextViewController,
delegate: GutterViewDelegate? = nil
) {
self.font = font
self.textColor = textColor
self.selectedLineTextColor = selectedTextColor ?? .secondaryLabelColor
self.textView = textView
self.textView = controller.textView
self.delegate = delegate

foldingRibbon = FoldingRibbonView(textView: textView, foldProvider: nil)
foldingRibbon = FoldingRibbonView(controller: controller, foldProvider: nil)

super.init(frame: .zero)
clipsToBounds = true
Expand Down

This file was deleted.

Loading