forked from github/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChatTemplateDropdownView.swift
More file actions
105 lines (99 loc) · 3.51 KB
/
ChatTemplateDropdownView.swift
File metadata and controls
105 lines (99 loc) · 3.51 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
import ConversationServiceProvider
import AppKit
import SwiftUI
public struct ChatTemplateDropdownView: View {
@Binding var templates: [ChatTemplate]
let onSelect: (ChatTemplate) -> Void
@State private var selectedIndex = 0
@State private var frameHeight: CGFloat = 0
@State private var localMonitor: Any? = nil
public var body: some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(Array(templates.enumerated()), id: \.element.id) { index, template in
HStack {
Text("/" + template.id)
.hoverPrimaryForeground(isHovered: selectedIndex == index)
Spacer()
Text(template.shortDescription)
.hoverSecondaryForeground(isHovered: selectedIndex == index)
}
.padding(.horizontal, 8)
.padding(.vertical, 6)
.contentShape(Rectangle())
.onTapGesture {
onSelect(template)
}
.hoverBackground(isHovered: selectedIndex == index)
.onHover { isHovered in
if isHovered {
selectedIndex = index
}
}
}
}
.background(
GeometryReader { geometry in
Color.clear
.onAppear { frameHeight = geometry.size.height }
.onChange(of: geometry.size.height) { newHeight in
frameHeight = newHeight
}
}
)
.background(.ultraThickMaterial)
.cornerRadius(6)
.shadow(radius: 2)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
)
.frame(maxWidth: .infinity)
.offset(y: -1 * frameHeight)
.onChange(of: templates) { _ in
selectedIndex = 0
}
.onAppear {
selectedIndex = 0
localMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
switch event.keyCode {
case 126: // Up arrow
moveSelection(up: true)
case 125: // Down arrow
moveSelection(up: false)
case 36: // Return key
handleEnter()
case 48: // Tab key
handleTab()
return nil // not forwarding the Tab Event which will replace the typed message to "\t"
default:
break
}
return event
}
}
.onDisappear {
if let monitor = localMonitor {
NSEvent.removeMonitor(monitor)
localMonitor = nil
}
}
}
private func moveSelection(up: Bool) {
guard !templates.isEmpty else { return }
let lowerBound = 0
let upperBound = templates.count - 1
let newIndex = selectedIndex + (up ? -1 : 1)
selectedIndex = newIndex < lowerBound ? upperBound : (newIndex > upperBound ? lowerBound : newIndex)
}
private func handleEnter() {
handleTemplateSelection()
}
private func handleTab() {
handleTemplateSelection()
}
private func handleTemplateSelection() {
if templates.count > 0 && selectedIndex < templates.count {
onSelect(templates[selectedIndex])
}
}
}