Skip to content

Commit 57df39a

Browse files
committed
Use custom text editor in chat for better performance
1 parent 69859c5 commit 57df39a

2 files changed

Lines changed: 152 additions & 63 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import SwiftUI
2+
3+
struct CustomTextEditor: NSViewRepresentable {
4+
func makeCoordinator() -> Coordinator {
5+
Coordinator(self)
6+
}
7+
8+
@Binding var text: String
9+
let font: NSFont
10+
let onSubmit: () -> Void
11+
12+
func makeNSView(context: Context) -> NSScrollView {
13+
let textView = (context.coordinator.theTextView.documentView as! NSTextView)
14+
textView.delegate = context.coordinator
15+
textView.string = text
16+
textView.font = font
17+
textView.allowsUndo = true
18+
textView.drawsBackground = false
19+
20+
return context.coordinator.theTextView
21+
}
22+
23+
func updateNSView(_ nsView: NSScrollView, context: Context) {
24+
let textView = (context.coordinator.theTextView.documentView as! NSTextView)
25+
textView.string = text
26+
}
27+
}
28+
29+
extension CustomTextEditor {
30+
class Coordinator: NSObject, NSTextViewDelegate {
31+
var view: CustomTextEditor
32+
var theTextView = NSTextView.scrollableTextView()
33+
var affectedCharRange: NSRange?
34+
35+
init(_ view: CustomTextEditor) {
36+
self.view = view
37+
}
38+
39+
func textDidChange(_ notification: Notification) {
40+
guard let textView = notification.object as? NSTextView else {
41+
return
42+
}
43+
44+
view.text = textView.string
45+
}
46+
47+
func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
48+
if commandSelector == #selector(NSTextView.insertNewline(_:)) {
49+
if let event = NSApplication.shared.currentEvent,
50+
!event.modifierFlags.contains(.shift),
51+
event.keyCode == 36
52+
{
53+
view.onSubmit()
54+
return true
55+
}
56+
}
57+
58+
return false
59+
}
60+
61+
func textView(
62+
_ textView: NSTextView,
63+
shouldChangeTextIn affectedCharRange: NSRange,
64+
replacementString: String?
65+
) -> Bool {
66+
return true
67+
}
68+
}
69+
}
70+

Core/Sources/SuggestionWidget/SuggestionPanelContent/ChatPanel.swift

Lines changed: 82 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -250,80 +250,99 @@ struct ChatPanelInputArea: View {
250250

251251
var body: some View {
252252
HStack {
253-
// clear button
254-
Button(action: {
255-
chat.clear()
256-
}) {
257-
Group {
258-
if #available(macOS 13.0, *) {
259-
Image(systemName: "eraser.line.dashed.fill")
260-
} else {
261-
Image(systemName: "trash.fill")
262-
}
263-
}
264-
.padding(6)
265-
.background {
266-
Circle().fill(Color(nsColor: .controlBackgroundColor))
267-
}
268-
.overlay {
269-
Circle()
270-
.stroke(Color(nsColor: .controlColor), lineWidth: 1)
271-
}
272-
}
273-
.buttonStyle(.plain)
253+
clearButton
254+
textEditor
255+
}
256+
.onAppear {
257+
isInputAreaFocused = true
258+
}
259+
.padding(8)
260+
.background(.ultraThickMaterial)
261+
}
274262

275-
HStack(spacing: 0) {
276-
Group {
277-
if #available(macOS 13.0, *) {
278-
TextField("Type a message", text: $typedMessage, axis: .vertical)
279-
} else {
280-
TextEditor(text: $typedMessage)
281-
.frame(height: 42, alignment: .leading)
282-
.font(.body)
283-
.background(Color.clear)
284-
}
285-
}
286-
.focused($isInputAreaFocused)
287-
.lineLimit(3)
288-
.multilineTextAlignment(.leading)
289-
.textFieldStyle(.plain)
290-
.padding(8)
291-
292-
Button(action: {
293-
if typedMessage.isEmpty { return }
294-
chat.send(typedMessage)
295-
typedMessage = ""
296-
}) {
297-
Image(systemName: "paperplane.fill")
298-
.padding(8)
263+
var clearButton: some View {
264+
Button(action: {
265+
chat.clear()
266+
}) {
267+
Group {
268+
if #available(macOS 13.0, *) {
269+
Image(systemName: "eraser.line.dashed.fill")
270+
} else {
271+
Image(systemName: "trash.fill")
299272
}
300-
.buttonStyle(.plain)
301-
.disabled(chat.isReceivingMessage)
302-
.keyboardShortcut(KeyEquivalent.return, modifiers: [])
303273
}
304-
.frame(maxWidth: .infinity)
274+
.padding(6)
305275
.background {
306-
RoundedRectangle(cornerRadius: 6)
307-
.fill(Color(nsColor: .controlBackgroundColor))
276+
Circle().fill(Color(nsColor: .controlBackgroundColor))
308277
}
309278
.overlay {
310-
RoundedRectangle(cornerRadius: 6)
279+
Circle()
311280
.stroke(Color(nsColor: .controlColor), lineWidth: 1)
312281
}
313-
.background {
314-
Button(action: {
315-
typedMessage += "\n"
316-
}) {
317-
EmptyView()
318-
}
319-
.keyboardShortcut(KeyEquivalent.return, modifiers: [.shift])
282+
}
283+
.buttonStyle(.plain)
284+
}
285+
286+
var textEditor: some View {
287+
HStack(spacing: 0) {
288+
ZStack(alignment: .center) {
289+
// a hack to support dynamic height of TextEditor
290+
Text(typedMessage.isEmpty ? "Hi" : typedMessage).opacity(0)
291+
.font(.system(size: 14))
292+
.frame(maxWidth: .infinity, maxHeight: 400)
293+
.overlay(alignment: .leadingFirstTextBaseline) {
294+
Text("Write something..")
295+
.foregroundColor(.secondary)
296+
.font(.system(size: 14))
297+
.opacity(typedMessage.isEmpty ? 1 : 0)
298+
}
299+
.padding(.top, 0)
300+
.padding(.bottom, 3)
301+
.padding(.horizontal, 4)
302+
303+
CustomTextEditor(
304+
text: $typedMessage,
305+
font: .systemFont(ofSize: 14),
306+
onSubmit: { submitText() }
307+
)
308+
}
309+
.focused($isInputAreaFocused)
310+
.padding(8)
311+
.fixedSize(horizontal: false, vertical: true)
312+
313+
Button(action: {
314+
submitText()
315+
}) {
316+
Image(systemName: "paperplane.fill")
317+
.padding(8)
320318
}
319+
.buttonStyle(.plain)
320+
.disabled(chat.isReceivingMessage)
321+
.keyboardShortcut(KeyEquivalent.return, modifiers: [])
321322
}
322-
.onAppear {
323-
isInputAreaFocused = true
323+
.frame(maxWidth: .infinity)
324+
.background {
325+
RoundedRectangle(cornerRadius: 6)
326+
.fill(Color(nsColor: .controlBackgroundColor))
324327
}
325-
.padding(8)
326-
.background(.ultraThickMaterial)
328+
.overlay {
329+
RoundedRectangle(cornerRadius: 6)
330+
.stroke(Color(nsColor: .controlColor), lineWidth: 1)
331+
}
332+
.background {
333+
Button(action: {
334+
typedMessage += "\n"
335+
}) {
336+
EmptyView()
337+
}
338+
.keyboardShortcut(KeyEquivalent.return, modifiers: [.shift])
339+
}
340+
}
341+
342+
func submitText() {
343+
if typedMessage.isEmpty { return }
344+
chat.send(typedMessage)
345+
typedMessage = ""
327346
}
328347
}
329348

0 commit comments

Comments
 (0)