Skip to content

Commit 8f517c9

Browse files
committed
Preview View, Tons Of Completion Window Stability Fixes
1 parent ee1b916 commit 8f517c9

File tree

9 files changed

+308
-103
lines changed

9 files changed

+308
-103
lines changed

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Views/MockCompletionDelegate.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,18 @@ class MockCompletionDelegate: CodeSuggestionDelegate, ObservableObject {
4848
class Suggestion: CodeSuggestionEntry {
4949
var label: String
5050
var detail: String?
51-
var pathComponents: [String]? { nil }
51+
var pathComponents: [String]?
5252
var targetPosition: CursorPosition? { nil }
53-
var sourcePreview: String? { nil }
53+
var sourcePreview: String?
5454
var image: Image = Image(systemName: "dot.square.fill")
5555
var imageColor: Color = .gray
5656
var deprecated: Bool = false
5757

58-
init(text: String) {
58+
init(text: String, detail: String?, sourcePreview: String?, pathComponents: [String]?) {
5959
self.label = text
60+
self.detail = detail
61+
self.sourcePreview = sourcePreview
62+
self.pathComponents = pathComponents
6063
}
6164
}
6265

@@ -67,7 +70,14 @@ class MockCompletionDelegate: CodeSuggestionDelegate, ObservableObject {
6770
let randomString = (0..<Int.random(in: 1..<text.count)).map {
6871
text[$0]
6972
}.shuffled().joined(separator: " ")
70-
suggestions.append(Suggestion(text: randomString))
73+
suggestions.append(
74+
Suggestion(
75+
text: randomString,
76+
detail: text.randomElement()!,
77+
sourcePreview: randomString,
78+
pathComponents: (0..<Int.random(in: 1..<5)).map { text[$0] }
79+
)
80+
)
7181
}
7282
return suggestions
7383
}

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Views/MockJumpToDefinitionDelegate.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,26 @@ import AppKit
99
import CodeEditSourceEditor
1010

1111
final class MockJumpToDefinitionDelegate: JumpToDefinitionDelegate, ObservableObject {
12-
func queryLinks(forRange range: NSRange) async -> [JumpToDefinitionLink]? {
13-
Bool.random() ? [
12+
func queryLinks(forRange range: NSRange, textView: TextViewController) async -> [JumpToDefinitionLink]? {
13+
[
1414
JumpToDefinitionLink(
1515
url: nil,
16-
targetPosition: CursorPosition(line: 0, column: 0),
17-
targetRange: NSRange(start: 0, end: 10),
16+
targetRange: CursorPosition(line: 0, column: 10),
1817
typeName: "Start of Document",
1918
sourcePreview: "// Comment at start"
2019
),
2120
JumpToDefinitionLink(
2221
url: URL(string: "https://codeedit.app/"),
23-
targetPosition: CursorPosition(line: 1024, column: 10),
24-
targetRange: NSRange(location: 30, length: 100),
22+
targetRange: CursorPosition(line: 1024, column: 10),
2523
typeName: "CodeEdit Website",
2624
sourcePreview: "https://codeedit.app/"
2725
)
28-
] : nil
26+
]
2927
}
3028

31-
func openLink(url: URL, targetRange: NSRange) {
32-
NSWorkspace.shared.open(url)
29+
func openLink(link: JumpToDefinitionLink) {
30+
if let url = link.url {
31+
NSWorkspace.shared.open(url)
32+
}
3333
}
3434
}

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Views/StatusBar.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,6 @@ struct StatusBar: View {
211211
}
212212

213213
// When there's a single cursor, display the line and column.
214-
return "Line: \(cursorPositions[0].line) Col: \(cursorPositions[0].column) Range: \(cursorPositions[0].range)"
214+
return "Line: \(cursorPositions[0].start.line) Col: \(cursorPositions[0].start.column) Range: \(cursorPositions[0].range)"
215215
}
216216
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//
2+
// CodeSuggestionPreviewView.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Khan Winter on 7/28/25.
6+
//
7+
8+
import SwiftUI
9+
10+
final class CodeSuggestionPreviewView: NSVisualEffectView {
11+
private let spacing: CGFloat = 5
12+
13+
var sourcePreview: String? {
14+
didSet {
15+
sourcePreviewLabel.stringValue = sourcePreview ?? ""
16+
sourcePreviewLabel.isHidden = sourcePreview == nil
17+
}
18+
}
19+
20+
var documentation: String? {
21+
didSet {
22+
documentationLabel.stringValue = documentation ?? ""
23+
documentationLabel.isHidden = documentation == nil
24+
}
25+
}
26+
27+
var pathComponents: [String] = [] {
28+
didSet {
29+
configurePathComponentsLabel()
30+
}
31+
}
32+
33+
var targetRange: CursorPosition? {
34+
didSet {
35+
configurePathComponentsLabel()
36+
}
37+
}
38+
39+
var font: NSFont = .systemFont(ofSize: 12) {
40+
didSet {
41+
sourcePreviewLabel.font = font
42+
pathComponentsLabel.font = font
43+
}
44+
}
45+
var documentationFont: NSFont = .systemFont(ofSize: 12) {
46+
didSet {
47+
documentationLabel.font = documentationFont
48+
}
49+
}
50+
51+
var stackView: NSStackView = NSStackView()
52+
var dividerView: NSView = NSView()
53+
var sourcePreviewLabel: NSTextField = NSTextField()
54+
var documentationLabel: NSTextField = NSTextField()
55+
var pathComponentsLabel: NSTextField = NSTextField()
56+
57+
init() {
58+
super.init(frame: .zero)
59+
stackView.translatesAutoresizingMaskIntoConstraints = false
60+
stackView.spacing = spacing
61+
stackView.orientation = .vertical
62+
stackView.alignment = .leading
63+
stackView.setContentCompressionResistancePriority(.required, for: .vertical)
64+
stackView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
65+
addSubview(stackView)
66+
67+
dividerView.translatesAutoresizingMaskIntoConstraints = false
68+
dividerView.wantsLayer = true
69+
dividerView.layer?.backgroundColor = NSColor.separatorColor.cgColor
70+
addSubview(dividerView)
71+
72+
self.material = .windowBackground
73+
self.blendingMode = .behindWindow
74+
75+
styleStaticLabel(sourcePreviewLabel)
76+
styleStaticLabel(documentationLabel)
77+
styleStaticLabel(pathComponentsLabel)
78+
79+
stackView.addArrangedSubview(sourcePreviewLabel)
80+
stackView.addArrangedSubview(documentationLabel)
81+
stackView.addArrangedSubview(pathComponentsLabel)
82+
83+
NSLayoutConstraint.activate([
84+
dividerView.topAnchor.constraint(equalTo: topAnchor),
85+
dividerView.leadingAnchor.constraint(equalTo: leadingAnchor),
86+
dividerView.trailingAnchor.constraint(equalTo: trailingAnchor),
87+
dividerView.heightAnchor.constraint(equalToConstant: 1),
88+
89+
stackView.topAnchor.constraint(equalTo: dividerView.bottomAnchor, constant: spacing),
90+
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -SuggestionController.WINDOW_PADDING),
91+
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 13),
92+
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -13)
93+
])
94+
}
95+
96+
required init?(coder: NSCoder) {
97+
fatalError("init(coder:) has not been implemented")
98+
}
99+
100+
func setPreferredMaxLayoutWidth(width: CGFloat) {
101+
sourcePreviewLabel.preferredMaxLayoutWidth = width
102+
documentationLabel.preferredMaxLayoutWidth = width
103+
pathComponentsLabel.preferredMaxLayoutWidth = width
104+
}
105+
106+
private func styleStaticLabel(_ label: NSTextField) {
107+
label.isEditable = false
108+
label.allowsDefaultTighteningForTruncation = false
109+
label.isBezeled = false
110+
label.isBordered = false
111+
label.backgroundColor = .clear
112+
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
113+
}
114+
115+
private func configurePathComponentsLabel() {
116+
// TODO: This
117+
pathComponentsLabel.isHidden = true
118+
}
119+
}

0 commit comments

Comments
 (0)