forked from github/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCustomTextEditor.swift
More file actions
179 lines (158 loc) · 5.9 KB
/
CustomTextEditor.swift
File metadata and controls
179 lines (158 loc) · 5.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import SwiftUI
public struct AutoresizingCustomTextEditor: View {
@Binding public var text: String
public let font: NSFont
public let isEditable: Bool
public let maxHeight: Double
public let onSubmit: () -> Void
public var completions: (_ text: String, _ words: [String], _ range: NSRange) -> [String]
public init(
text: Binding<String>,
font: NSFont,
isEditable: Bool,
maxHeight: Double,
onSubmit: @escaping () -> Void,
completions: @escaping (_ text: String, _ words: [String], _ range: NSRange)
-> [String] = { _, _, _ in [] }
) {
_text = text
self.font = font
self.isEditable = isEditable
self.maxHeight = maxHeight
self.onSubmit = onSubmit
self.completions = completions
}
public var body: some View {
ZStack(alignment: .center) {
// a hack to support dynamic height of TextEditor
Text(text.isEmpty ? "Hi" : text).opacity(0)
.font(.init(font))
.frame(maxWidth: .infinity, maxHeight: maxHeight)
.padding(.top, 1)
.padding(.bottom, 2)
.padding(.horizontal, 4)
CustomTextEditor(
text: $text,
font: font,
maxHeight: maxHeight,
onSubmit: onSubmit,
completions: completions
)
.padding(.top, 1)
.padding(.bottom, -1)
}
}
}
public struct CustomTextEditor: NSViewRepresentable {
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
@Binding public var text: String
public let font: NSFont
public let maxHeight: Double
public let isEditable: Bool
public let onSubmit: () -> Void
public var completions: (_ text: String, _ words: [String], _ range: NSRange) -> [String]
public init(
text: Binding<String>,
font: NSFont,
isEditable: Bool = true,
maxHeight: Double,
onSubmit: @escaping () -> Void,
completions: @escaping (_ text: String, _ words: [String], _ range: NSRange)
-> [String] = { _, _, _ in [] }
) {
_text = text
self.font = font
self.isEditable = isEditable
self.maxHeight = maxHeight
self.onSubmit = onSubmit
self.completions = completions
}
public func makeNSView(context: Context) -> NSScrollView {
// context.coordinator.completions = completions
let textView = (context.coordinator.theTextView.documentView as! NSTextView)
textView.delegate = context.coordinator
textView.string = text
textView.font = font
textView.allowsUndo = true
textView.drawsBackground = false
textView.isAutomaticQuoteSubstitutionEnabled = false
textView.isAutomaticDashSubstitutionEnabled = false
textView.isAutomaticTextReplacementEnabled = false
// Configure scroll view
let scrollView = context.coordinator.theTextView
scrollView.hasHorizontalScroller = false
context.coordinator.observeHeight(scrollView: scrollView, maxHeight: maxHeight)
return scrollView
}
public func updateNSView(_ nsView: NSScrollView, context: Context) {
// context.coordinator.completions = completions
let textView = (context.coordinator.theTextView.documentView as! NSTextView)
textView.isEditable = isEditable
guard textView.string != text else { return }
textView.string = text
textView.undoManager?.removeAllActions()
}
}
public extension CustomTextEditor {
class Coordinator: NSObject, NSTextViewDelegate {
var view: CustomTextEditor
var theTextView = NSTextView.scrollableTextView()
var affectedCharRange: NSRange?
var completions: (String, [String], _ range: NSRange) -> [String] = { _, _, _ in [] }
var heightObserver: NSKeyValueObservation?
init(_ view: CustomTextEditor) {
self.view = view
}
public func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
view.text = textView.string
textView.complete(nil)
}
public func textView(
_ textView: NSTextView,
doCommandBy commandSelector: Selector
) -> Bool {
if commandSelector == #selector(NSTextView.insertNewline(_:)) {
if let event = NSApplication.shared.currentEvent,
!event.modifierFlags.contains(.shift),
event.keyCode == 36 // enter
{
view.onSubmit()
return true
}
}
return false
}
public func textView(
_ textView: NSTextView,
shouldChangeTextIn affectedCharRange: NSRange,
replacementString: String?
) -> Bool {
return true
}
public func textView(
_ textView: NSTextView,
completions words: [String],
forPartialWordRange charRange: NSRange,
indexOfSelectedItem index: UnsafeMutablePointer<Int>?
) -> [String] {
index?.pointee = -1
return completions(textView.textStorage?.string ?? "", words, charRange)
}
func observeHeight(scrollView: NSScrollView, maxHeight: Double) {
let textView = scrollView.documentView as! NSTextView
heightObserver = textView.observe(\NSTextView.frame) { [weak scrollView] _, _ in
guard let scrollView = scrollView else { return }
let contentHeight = textView.frame.height
scrollView.hasVerticalScroller = contentHeight >= maxHeight
}
}
deinit {
heightObserver?.invalidate()
}
}
}