@@ -2,29 +2,40 @@ import LaunchAgentManager
22import Preferences
33import SwiftUI
44
5- final class Settings : ObservableObject {
6- @AppStorage ( \. quitXPCServiceOnXcodeAndAppQuit)
7- var quitXPCServiceOnXcodeAndAppQuit : Bool
8- @AppStorage ( \. realtimeSuggestionToggle)
9- var realtimeSuggestionToggle : Bool
10- @AppStorage ( \. realtimeSuggestionDebounce)
11- var realtimeSuggestionDebounce : Double
12- @AppStorage ( \. suggestionPresentationMode)
13- var suggestionPresentationMode : Preferences . PresentationMode
14- @AppStorage ( \. suggestionWidgetPositionMode)
15- var suggestionWidgetPositionMode : SuggestionWidgetPositionMode
16- @AppStorage ( \. widgetColorScheme)
17- var widgetColorScheme : WidgetColorScheme
18- @AppStorage ( \. acceptSuggestionWithAccessibilityAPI)
19- var acceptSuggestionWithAccessibilityAPI : Bool
20- init ( ) { }
21- }
22-
235struct SettingsView : View {
6+ final class Settings : ObservableObject {
7+ @AppStorage ( \. quitXPCServiceOnXcodeAndAppQuit)
8+ var quitXPCServiceOnXcodeAndAppQuit : Bool
9+ @AppStorage ( \. realtimeSuggestionToggle)
10+ var realtimeSuggestionToggle : Bool
11+ @AppStorage ( \. realtimeSuggestionDebounce)
12+ var realtimeSuggestionDebounce : Double
13+ @AppStorage ( \. suggestionPresentationMode)
14+ var suggestionPresentationMode : Preferences . PresentationMode
15+ @AppStorage ( \. suggestionWidgetPositionMode)
16+ var suggestionWidgetPositionMode : SuggestionWidgetPositionMode
17+ @AppStorage ( \. widgetColorScheme)
18+ var widgetColorScheme : WidgetColorScheme
19+ @AppStorage ( \. acceptSuggestionWithAccessibilityAPI)
20+ var acceptSuggestionWithAccessibilityAPI : Bool
21+ @AppStorage ( \. disableSuggestionFeatureGlobally)
22+ var disableSuggestionFeatureGlobally : Bool
23+ @AppStorage ( \. suggestionFeatureEnabledProjectList)
24+ var suggestionFeatureEnabledProjectList : [ String ]
25+ @AppStorage ( \. promptToCodeFeatureProvider)
26+ var promptToCodeFeatureProvider : PromptToCodeFeatureProvider
27+ @AppStorage ( \. preferWidgetToStayInsideEditorWhenWidthGreaterThan)
28+ var preferWidgetToStayInsideEditorWhenWidthGreaterThan : Double
29+ @AppStorage ( \. hideCommonPrecedingSpacesInSuggestion)
30+ var hideCommonPrecedingSpacesInSuggestion : Bool
31+ init ( ) { }
32+ }
33+
2434 @StateObject var settings = Settings ( )
2535 @State var editingRealtimeSuggestionDebounce : Double = UserDefaults . shared
2636 . value ( for: \. realtimeSuggestionDebounce)
2737 @Environment ( \. updateChecker) var updateChecker
38+ @State var isSuggestionFeatureEnabledListPickerOpen = false
2839
2940 var body : some View {
3041 Section {
@@ -85,43 +96,222 @@ struct SettingsView: View {
8596 }
8697 }
8798
88- Toggle ( isOn: $settings. realtimeSuggestionToggle) {
89- Text ( " Real-time suggestion " )
99+ Group {
100+ Toggle ( isOn: $settings. realtimeSuggestionToggle) {
101+ Text ( " Real-time suggestion " )
102+ }
103+ . toggleStyle ( . switch)
104+
105+ HStack {
106+ Toggle ( isOn: $settings. disableSuggestionFeatureGlobally) {
107+ Text ( " Disable suggestion feature globally " )
108+ }
109+ . toggleStyle ( . switch)
110+
111+ Button ( " Enabled Projects " ) {
112+ isSuggestionFeatureEnabledListPickerOpen = true
113+ }
114+ } . sheet ( isPresented: $isSuggestionFeatureEnabledListPickerOpen) {
115+ SuggestionFeatureEnabledProjectListView (
116+ isOpen: $isSuggestionFeatureEnabledListPickerOpen
117+ )
118+ }
119+
120+ HStack {
121+ Slider ( value: $editingRealtimeSuggestionDebounce, in: 0 ... 2 , step: 0.1 ) {
122+ Text ( " Real-time suggestion fetch debounce " )
123+ } onEditingChanged: { _ in
124+ settings. realtimeSuggestionDebounce = editingRealtimeSuggestionDebounce
125+ }
126+
127+ Text (
128+ " \( editingRealtimeSuggestionDebounce. formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) s "
129+ )
130+ . font ( . body)
131+ . monospacedDigit ( )
132+ . padding ( . vertical, 2 )
133+ . padding ( . horizontal, 6 )
134+ . background (
135+ RoundedRectangle ( cornerRadius: 4 , style: . continuous)
136+ . fill ( Color . white. opacity ( 0.2 ) )
137+ )
138+ }
139+
140+ Toggle ( isOn: $settings. hideCommonPrecedingSpacesInSuggestion) {
141+ Text ( " Hide Common Preceding Spaces in Suggestion " )
142+ }
143+ . toggleStyle ( . switch)
144+
145+ Toggle ( isOn: $settings. acceptSuggestionWithAccessibilityAPI) {
146+ Text ( " Use accessibility API to accept suggestion in widget " )
147+ }
148+ . toggleStyle ( . switch)
149+ }
150+
151+ Picker ( selection: $settings. promptToCodeFeatureProvider) {
152+ ForEach ( PromptToCodeFeatureProvider . allCases, id: \. rawValue) {
153+ switch $0 {
154+ case . openAI:
155+ Text ( " OpenAI " ) . tag ( $0)
156+ case . githubCopilot:
157+ Text ( " GitHub Copilot (Less Accurate) " ) . tag ( $0)
158+ }
159+ }
160+ } label: {
161+ Text ( " Prompt to code with " )
90162 }
91- . toggleStyle ( . switch)
92163
93164 HStack {
94- Slider ( value: $editingRealtimeSuggestionDebounce, in: 0 ... 2 , step: 0.1 ) {
95- Text ( " Real-time suggestion fetch debounce " )
96- } onEditingChanged: { _ in
97- settings. realtimeSuggestionDebounce = editingRealtimeSuggestionDebounce
165+ TextField ( text: . init( get: {
166+ " \( Int ( settings. preferWidgetToStayInsideEditorWhenWidthGreaterThan) ) "
167+ } , set: {
168+ settings
169+ . preferWidgetToStayInsideEditorWhenWidthGreaterThan =
170+ Double ( Int ( $0) ?? 0 )
171+ } ) ) {
172+ Text ( " Prefer widget to be inside editor when width greater than " )
98173 }
174+ . textFieldStyle ( . roundedBorder)
99175
100- Text (
101- " \( editingRealtimeSuggestionDebounce. formatted ( . number. precision ( . fractionLength( 2 ) ) ) ) s "
102- )
103- . font ( . body)
104- . monospacedDigit ( )
105- . padding ( . vertical, 2 )
106- . padding ( . horizontal, 6 )
107- . background (
108- RoundedRectangle ( cornerRadius: 4 , style: . continuous)
109- . fill ( Color . white. opacity ( 0.2 ) )
110- )
111- }
112-
113- Toggle ( isOn: $settings. acceptSuggestionWithAccessibilityAPI) {
114- Text ( " Use accessibility API to accept suggestion in widget " )
176+ Text ( " px " )
115177 }
116- . toggleStyle ( . switch)
117178 }
118179 } . buttonStyle ( . copilot)
119180 }
120181}
121182
183+ struct SuggestionFeatureEnabledProjectListView : View {
184+ final class Settings : ObservableObject {
185+ @AppStorage ( \. suggestionFeatureEnabledProjectList)
186+ var suggestionFeatureEnabledProjectList : [ String ]
187+
188+ init ( suggestionFeatureEnabledProjectList: AppStorage < [ String ] > ? = nil ) {
189+ if let list = suggestionFeatureEnabledProjectList {
190+ _suggestionFeatureEnabledProjectList = list
191+ }
192+ }
193+ }
194+
195+ var isOpen : Binding < Bool >
196+ @State var isAddingNewProject = false
197+ @StateObject var settings = Settings ( )
198+
199+ var body : some View {
200+ VStack {
201+ HStack {
202+ Button ( action: {
203+ self . isOpen. wrappedValue = false
204+ } ) {
205+ Image ( systemName: " xmark.circle.fill " )
206+ . foregroundStyle ( . secondary)
207+ . padding ( )
208+ }
209+ . buttonStyle ( . plain)
210+ Text ( " Enabled Projects " )
211+ Spacer ( )
212+ Button ( action: {
213+ isAddingNewProject = true
214+ } ) {
215+ Image ( systemName: " plus.circle.fill " )
216+ . foregroundStyle ( . secondary)
217+ . padding ( )
218+ }
219+ . buttonStyle ( . plain)
220+ }
221+ . background ( . black. opacity ( 0.2 ) )
222+
223+ List {
224+ ForEach (
225+ settings. suggestionFeatureEnabledProjectList,
226+ id: \. self
227+ ) { project in
228+ HStack {
229+ Text ( project)
230+ . contextMenu {
231+ Button ( " Remove " ) {
232+ settings. suggestionFeatureEnabledProjectList. removeAll (
233+ where: { $0 == project }
234+ )
235+ }
236+ }
237+ Spacer ( )
238+
239+ Button ( action: {
240+ settings. suggestionFeatureEnabledProjectList. removeAll (
241+ where: { $0 == project }
242+ )
243+ } ) {
244+ Image ( systemName: " trash.fill " )
245+ . foregroundStyle ( . secondary)
246+ }
247+ . buttonStyle ( . plain)
248+ }
249+ }
250+ }
251+ . overlay {
252+ if settings. suggestionFeatureEnabledProjectList. isEmpty {
253+ Text ( """
254+ Empty
255+ Add project with " + " button
256+ Or right clicking the circular widget
257+ """ )
258+ . multilineTextAlignment ( . center)
259+ }
260+ }
261+ }
262+ . frame ( width: 300 , height: 400 )
263+ . sheet ( isPresented: $isAddingNewProject) {
264+ SuggestionFeatureAddEnabledProjectView ( isOpen: $isAddingNewProject, settings: settings)
265+ }
266+ }
267+ }
268+
269+ struct SuggestionFeatureAddEnabledProjectView : View {
270+ var isOpen : Binding < Bool >
271+ var settings : SuggestionFeatureEnabledProjectListView . Settings
272+ @State var rootPath = " "
273+
274+ var body : some View {
275+ VStack {
276+ Text (
277+ " Enter the root path of the project. Do not use `~` to replace /Users/yourUserName. "
278+ )
279+ TextField ( " Root path " , text: $rootPath)
280+ HStack {
281+ Spacer ( )
282+ Button ( " Cancel " ) {
283+ isOpen. wrappedValue = false
284+ }
285+ Button ( " Add " ) {
286+ settings. suggestionFeatureEnabledProjectList. append ( rootPath)
287+ isOpen. wrappedValue = false
288+ }
289+ } . buttonStyle ( . copilot)
290+ }
291+ . padding ( )
292+ . frame ( minWidth: 500 )
293+ }
294+ }
295+
296+ // MARK: - Previews
297+
122298struct SettingsView_Preview : PreviewProvider {
123299 static var previews : some View {
124300 SettingsView ( )
125301 . background ( . purple)
126302 }
127303}
304+
305+ struct SuggestionFeatureEnabledProjectListView_Preview : PreviewProvider {
306+ static var previews : some View {
307+ SuggestionFeatureEnabledProjectListView (
308+ isOpen: . constant( true ) ,
309+ settings: . init( suggestionFeatureEnabledProjectList: . init( wrappedValue: [
310+ " hello/2 " ,
311+ " hello/3 " ,
312+ " hello/4 " ,
313+ ] , " SuggestionFeatureEnabledProjectListView_Preview " ) )
314+ )
315+ . background ( . purple)
316+ }
317+ }
0 commit comments