native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Hide script placeholders while editors are focused

onevcat 9ca2ef8b 005e5ccf

+87 -9
+61 -8
supacode/Features/Settings/Views/RepositorySettingsView.swift
··· 18 18 @State private var popoverRefocusTask: Task<Void, Never>? 19 19 @State private var commandEditorCommandID: UserCustomCommand.ID? 20 20 @State private var editingNameCommandID: UserCustomCommand.ID? 21 + @State private var focusedScriptEditor: ScriptEditorFocus? 21 22 @FocusState private var focusedNameEditorCommandID: UserCustomCommand.ID? 22 23 23 24 private let keyTokenResolver = ShortcutKeyTokenResolver() ··· 48 49 "cloud.fill", 49 50 "tray.and.arrow.down.fill", 50 51 ] 52 + 53 + private enum ScriptEditorFocus: Equatable { 54 + case setup 55 + case archive 56 + case run 57 + case command(UserCustomCommand.ID) 58 + } 51 59 52 60 var body: some View { 53 61 let baseRefOptions = ··· 169 177 Section { 170 178 ZStack(alignment: .topLeading) { 171 179 PlainTextEditor( 172 - text: settings.setupScript 180 + text: settings.setupScript, 181 + onFocusChange: { isFocused in 182 + if isFocused { 183 + focusedScriptEditor = .setup 184 + } else if focusedScriptEditor == .setup { 185 + focusedScriptEditor = nil 186 + } 187 + } 173 188 ) 174 189 .frame(minHeight: 120) 175 - if store.settings.setupScript.isEmpty { 190 + if store.settings.setupScript.isEmpty, 191 + focusedScriptEditor != .setup 192 + { 176 193 Text("claude --dangerously-skip-permissions") 177 194 .foregroundStyle(.secondary) 178 195 .padding(.leading, plainTextPlaceholderLeading) ··· 194 211 Section { 195 212 ZStack(alignment: .topLeading) { 196 213 PlainTextEditor( 197 - text: settings.archiveScript 214 + text: settings.archiveScript, 215 + onFocusChange: { isFocused in 216 + if isFocused { 217 + focusedScriptEditor = .archive 218 + } else if focusedScriptEditor == .archive { 219 + focusedScriptEditor = nil 220 + } 221 + } 198 222 ) 199 223 .frame(minHeight: 120) 200 - if store.settings.archiveScript.isEmpty { 224 + if store.settings.archiveScript.isEmpty, 225 + focusedScriptEditor != .archive 226 + { 201 227 Text("docker compose down") 202 228 .foregroundStyle(.secondary) 203 229 .padding(.leading, plainTextPlaceholderLeading) ··· 219 245 Section { 220 246 ZStack(alignment: .topLeading) { 221 247 PlainTextEditor( 222 - text: settings.runScript 248 + text: settings.runScript, 249 + onFocusChange: { isFocused in 250 + if isFocused { 251 + focusedScriptEditor = .run 252 + } else if focusedScriptEditor == .run { 253 + focusedScriptEditor = nil 254 + } 255 + } 223 256 ) 224 257 .frame(minHeight: 120) 225 - if store.settings.runScript.isEmpty { 258 + if store.settings.runScript.isEmpty, 259 + focusedScriptEditor != .run 260 + { 226 261 Text("npm run dev") 227 262 .foregroundStyle(.secondary) 228 263 .padding(.leading, plainTextPlaceholderLeading) ··· 289 324 popoverRefocusTask?.cancel() 290 325 popoverRefocusTask = nil 291 326 focusedNameEditorCommandID = nil 327 + focusedScriptEditor = nil 292 328 } 293 329 .alert( 294 330 "Shortcut Conflict", ··· 714 750 PlainTextEditor( 715 751 text: command.command, 716 752 isMonospaced: true, 717 - shouldFocus: true 753 + shouldFocus: true, 754 + onFocusChange: { isFocused in 755 + let field = ScriptEditorFocus.command(command.wrappedValue.id) 756 + if isFocused { 757 + focusedScriptEditor = field 758 + } else if focusedScriptEditor == field { 759 + focusedScriptEditor = nil 760 + } 761 + } 718 762 ) 719 763 .frame(height: 140) 720 764 721 - if command.wrappedValue.command.isEmpty { 765 + if command.wrappedValue.command.isEmpty, 766 + focusedScriptEditor != .command(command.wrappedValue.id) 767 + { 722 768 Text(scriptPlaceholder(for: command.wrappedValue.execution)) 723 769 .foregroundStyle(.secondary) 724 770 .padding(.leading, plainTextPlaceholderLeading) ··· 913 959 commandEditorCommandID = nil 914 960 editingNameCommandID = nil 915 961 focusedNameEditorCommandID = nil 962 + focusedScriptEditor = nil 916 963 return 917 964 } 918 965 ··· 953 1000 { 954 1001 self.editingNameCommandID = nil 955 1002 focusedNameEditorCommandID = nil 1003 + } 1004 + 1005 + if case let .command(commandID) = focusedScriptEditor, 1006 + !validIDs.contains(commandID) 1007 + { 1008 + focusedScriptEditor = nil 956 1009 } 957 1010 } 958 1011
+26 -1
supacode/Support/PlainTextEditor.swift
··· 5 5 @Binding var text: String 6 6 var isMonospaced: Bool = false 7 7 var shouldFocus: Bool = false 8 + var onFocusChange: ((Bool) -> Void)? = nil 8 9 9 10 func makeCoordinator() -> Coordinator { 10 11 Coordinator(text: $text) 11 12 } 12 13 13 14 func makeNSView(context: Context) -> NSScrollView { 14 - let textView = NSTextView(frame: .zero) 15 + let textView = FocusAwareTextView(frame: .zero) 15 16 textView.delegate = context.coordinator 17 + textView.onFocusChange = onFocusChange 16 18 textView.drawsBackground = false 17 19 textView.isRichText = false 18 20 textView.importsGraphics = false ··· 42 44 43 45 func updateNSView(_ nsView: NSScrollView, context: Context) { 44 46 guard let textView = nsView.documentView as? NSTextView else { return } 47 + if let focusAwareTextView = textView as? FocusAwareTextView { 48 + focusAwareTextView.onFocusChange = onFocusChange 49 + } 45 50 if textView.string != text { 46 51 textView.string = text 47 52 } ··· 73 78 func textDidChange(_ notification: Notification) { 74 79 guard let textView = notification.object as? NSTextView else { return } 75 80 text = textView.string 81 + } 82 + } 83 + 84 + final class FocusAwareTextView: NSTextView { 85 + var onFocusChange: ((Bool) -> Void)? 86 + 87 + override func becomeFirstResponder() -> Bool { 88 + let didBecomeFirstResponder = super.becomeFirstResponder() 89 + if didBecomeFirstResponder { 90 + onFocusChange?(true) 91 + } 92 + return didBecomeFirstResponder 93 + } 94 + 95 + override func resignFirstResponder() -> Bool { 96 + let didResignFirstResponder = super.resignFirstResponder() 97 + if didResignFirstResponder { 98 + onFocusChange?(false) 99 + } 100 + return didResignFirstResponder 76 101 } 77 102 } 78 103 }