From 8a4ac6ac3d62c88ea99ce2ae20a7e1a1b8f32223 Mon Sep 17 00:00:00 2001 From: Dallas Groot Date: Sun, 12 Apr 2026 00:53:48 -0700 Subject: [PATCH 1/3] commit --- .DS_Store | Bin 0 -> 6148 bytes PadXcode.xcodeproj/project.pbxproj | 678 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/swiftpm/Package.resolved | 33 + .../UserInterfaceState.xcuserstate | Bin 0 -> 25620 bytes .../xcschemes/xcschememanagement.plist | 14 + 6 files changed, 732 insertions(+) create mode 100644 .DS_Store create mode 100644 PadXcode.xcodeproj/project.pbxproj create mode 100644 PadXcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 PadXcode.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 PadXcode.xcodeproj/project.xcworkspace/xcuserdata/dallasgroot.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 PadXcode.xcodeproj/xcuserdata/dallasgroot.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b0365f69dbf3d7c6a003bf6106e5c4eb10100f34 GIT binary patch literal 6148 zcmeHKOG*Pl5UtW#47v%fT=ofs+@KT2y^xJdO-2++m_ZO+_SqEi0G=j0UwtAm&LX%F zkt*nZHP!X{^I-ZzL_B?L$3!C{s!;@4oB@;YVAFvoS3uT27SvHoH*`i%10DUvkj(vr zF6bKbzNGE>Z~UGfu&Vpz?Rd86R=q7^{k$)4)iuq0+0Bt(SL^qqm)Ez+{@nJ*-2QHB zzML^+SSSz*gaV;JD4+`9%oeMU4WkYPLV-}=Qvul@5{h7UEQY#upwcG*P_EHx@Z~Kb zoaC4tiy>NIY*e68*`F9}bi|YAWyfM@bYdMD{rJe6#|!J|h$l^_xcQn6QHKJ%3LIKs zBUn#&L7AI$ILb*Z_le{7h4E~o(06t_NIn_pM bPvRplI~GIfMdC^a#zjB{i7FKM1qD6;3Fbfu literal 0 HcmV?d00001 diff --git a/PadXcode.xcodeproj/project.pbxproj b/PadXcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000..8868a81 --- /dev/null +++ b/PadXcode.xcodeproj/project.pbxproj @@ -0,0 +1,678 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 083FF8D3F7D7C404292CC778 /* LanguageDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 486323C78B9C82E619CA733D /* LanguageDetector.swift */; }; + 152AF7634AD26B9DB0C0C0E2 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772CFF753C6F3A5ACBE3ADB3 /* ContentView.swift */; }; + 1733074A4A501358F74BA56C /* FileOperationValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C0BB28FA3672ACCF8B1F18 /* FileOperationValidator.swift */; }; + 1958662EADCBB69352191A3E /* FindInFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9F83681CE5ECA07416A9FA /* FindInFileView.swift */; }; + 1B14F98EC8A4E5141A15BF12 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8353DAEA46503C5282B7B130 /* OnboardingView.swift */; }; + 23CD88A842BF5B264AF834DF /* PreflightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6151BCED355A7DB45874354 /* PreflightView.swift */; }; + 2A5AC5BE78730EFB7EB5D13C /* FindAcrossFilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24AEDD9354F0B2D5F9529B63 /* FindAcrossFilesView.swift */; }; + 2D078ABFBD23CEE45DB030D7 /* FileSyncPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F973FFA66BA97FB15693831 /* FileSyncPipeline.swift */; }; + 338405877BC9B112C1A5583B /* TreeSitterBashRunestone in Frameworks */ = {isa = PBXBuildFile; productRef = 956726714707A9B39225C5A6 /* TreeSitterBashRunestone */; }; + 33C2A48DAF7FC9D16CDF640F /* GitPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772FA6F31FCD4165D27B03B8 /* GitPanel.swift */; }; + 36907E0F5E04B4AC0B4A02BD /* PadXcodeTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6131F64D27F3F414498EE33 /* PadXcodeTheme.swift */; }; + 371D5862E0FC9D88AE10E0B1 /* GitStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7F6CB2C7736B088EAE7A3 /* GitStore.swift */; }; + 403E2F72B0BABD711A511638 /* ToastSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F35A0DBED65BE54BE0E9DB /* ToastSystem.swift */; }; + 4457404AF277B3BF0FD0EAAD /* EditorTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89E2479AE0F335916A182D21 /* EditorTabBar.swift */; }; + 4BB903BFD791318133D6A069 /* CodeEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7807AF0FAB473BDE4EEE42D /* CodeEditorView.swift */; }; + 573D6A16ACD1B78394565880 /* DaemonConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F2B8E63953C26B44921E94 /* DaemonConfiguration.swift */; }; + 57F8F70B263E6097FF0E60C8 /* UnsavedChangesGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86C371B51B38EBEF37CC9187 /* UnsavedChangesGuard.swift */; }; + 5DEB0C4381E2506328432D78 /* CompletionPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73AED5B4DD104E0170E7B995 /* CompletionPopover.swift */; }; + 651C31BD6316AD08A89759C4 /* SharedModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 040994AC7B1C480C01312210 /* SharedModels.swift */; }; + 6926E7BA074643381A03EBF2 /* CompletionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1197CE2D262CAB36A4EB08B1 /* CompletionController.swift */; }; + 7C5CED6B1BB53D0BEF24A071 /* TreeSitterJSONRunestone in Frameworks */ = {isa = PBXBuildFile; productRef = E47877F105A71A00A5028F8F /* TreeSitterJSONRunestone */; }; + 81115284C29C9884270CCF68 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5840FC45E969239D7B2B34 /* SettingsView.swift */; }; + 86695EF4C142827F9A7E5262 /* Runestone in Frameworks */ = {isa = PBXBuildFile; productRef = 3120FC610B14411603CDF871 /* Runestone */; }; + 88EDF52AD32D156D991366DD /* GutterAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0065F9C4376D6BC348C0314 /* GutterAnnotationView.swift */; }; + 90C3D4D8698EE2901CDF6487 /* EnhancedConsoleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A1BF00F860C37D9DC8C613 /* EnhancedConsoleView.swift */; }; + 93BBC37FBE9490A3863319D9 /* EditorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 949E76D0BDDF5F0CCBA337B2 /* EditorState.swift */; }; + 9E5911ECB267D71446335BAC /* GitCallbackHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC68A23144FCD4E447D67C01 /* GitCallbackHandler.swift */; }; + A2FB9D4EEB36892347BEF121 /* PadXcodeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A099AC677DB833A24E5B11F6 /* PadXcodeApp.swift */; }; + A745764D69CD7065491307FD /* DaemonHealthMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01699A9B4132700BCED77AF /* DaemonHealthMonitor.swift */; }; + AB67A1DBA578A7364FE7E0E2 /* GitService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA68C429721ACAC7F5762A0E /* GitService.swift */; }; + ACB9ACF2E46075B1DCD0FF9F /* ProjectStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72C6481C52141ED76295443F /* ProjectStore.swift */; }; + BC73C1C7C7900157D9771F0B /* BuildToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7A097BE571671DFC91687F5 /* BuildToolbar.swift */; }; + BCC8A449CD3D69A848DBFF51 /* LocalGitConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0ADB757F3F9FD0EEDE0792 /* LocalGitConfiguration.swift */; }; + C03314EAF4EC702A21E41D17 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3760369B89F6A8D0494672 /* SceneDelegate.swift */; }; + C6139C1D648C21FE025EAAF8 /* RetryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BBB5694C44453C03F81564 /* RetryPolicy.swift */; }; + C6BACCBCDECFAF935E59078A /* BuildDiagnosticParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F592A988E8CAD87E667522 /* BuildDiagnosticParser.swift */; }; + CA9FB2823282C67850985CDA /* FileOperationSheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 802D34EBD792211122BFFAD1 /* FileOperationSheets.swift */; }; + CB9FEAD7791E0F024861295A /* JumpToLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C6CF7ECB54685991BF2756 /* JumpToLineView.swift */; }; + CE20F98B2486E1E153DDE6CA /* TreeSitterMarkdownRunestone in Frameworks */ = {isa = PBXBuildFile; productRef = BC622C188F819128EFAD41FC /* TreeSitterMarkdownRunestone */; }; + DE1D50F19A7356757663936E /* EditorKeyboardToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F9E7D37468DC86AA0FE9EA4 /* EditorKeyboardToolbar.swift */; }; + E052E548B69DEAE08B676FBC /* LSPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C80ED0B1C6FB0AE8B7D525F /* LSPClient.swift */; }; + E31CC0E6168F30611E2EA728 /* BuildPreflightChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9020C062BD9713290C885D1 /* BuildPreflightChecker.swift */; }; + E47EAEA1E890FF698EDAD30C /* BuildHistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5247AA5BF912B8922DCB52 /* BuildHistoryStore.swift */; }; + E572F4D5B59D51F7026DDBC1 /* TreeSitterSwiftRunestone in Frameworks */ = {isa = PBXBuildFile; productRef = 8410B0D892289F9738E1C07D /* TreeSitterSwiftRunestone */; }; + EF158635704532F420B9A4EA /* BuildService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3A46BFD2F8AEFA0A78AE0D6 /* BuildService.swift */; }; + F055206CFF5CC3DD2FF589CE /* GitAwareFileNavigatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE12DC24C78ACBF394E7F0B /* GitAwareFileNavigatorView.swift */; }; + F5E24A92C0FC384F7BE75F19 /* WorkingCopyGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D898CA50CF77E9D99DF46861 /* WorkingCopyGuard.swift */; }; + F7503FE6FDAF26BCFF4E45D7 /* RunestoneEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B107E8DEB3E6DEF57F78FF84 /* RunestoneEditorView.swift */; }; + FD74914AEA0642D536E3505F /* HardwareKeyboardCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 403212EB7771BE3BDA8D2824 /* HardwareKeyboardCommandHandler.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 03BBB5694C44453C03F81564 /* RetryPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryPolicy.swift; sourceTree = ""; }; + 040994AC7B1C480C01312210 /* SharedModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedModels.swift; sourceTree = ""; }; + 1197CE2D262CAB36A4EB08B1 /* CompletionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionController.swift; sourceTree = ""; }; + 1F973FFA66BA97FB15693831 /* FileSyncPipeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSyncPipeline.swift; sourceTree = ""; }; + 24AEDD9354F0B2D5F9529B63 /* FindAcrossFilesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindAcrossFilesView.swift; sourceTree = ""; }; + 2FE12DC24C78ACBF394E7F0B /* GitAwareFileNavigatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitAwareFileNavigatorView.swift; sourceTree = ""; }; + 32F592A988E8CAD87E667522 /* BuildDiagnosticParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDiagnosticParser.swift; sourceTree = ""; }; + 3A5247AA5BF912B8922DCB52 /* BuildHistoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildHistoryStore.swift; sourceTree = ""; }; + 3C9F83681CE5ECA07416A9FA /* FindInFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindInFileView.swift; sourceTree = ""; }; + 403212EB7771BE3BDA8D2824 /* HardwareKeyboardCommandHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardwareKeyboardCommandHandler.swift; sourceTree = ""; }; + 486323C78B9C82E619CA733D /* LanguageDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageDetector.swift; sourceTree = ""; }; + 4C80ED0B1C6FB0AE8B7D525F /* LSPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LSPClient.swift; sourceTree = ""; }; + 54A1BF00F860C37D9DC8C613 /* EnhancedConsoleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedConsoleView.swift; sourceTree = ""; }; + 61F2B8E63953C26B44921E94 /* DaemonConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaemonConfiguration.swift; sourceTree = ""; }; + 6D5840FC45E969239D7B2B34 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 72C6481C52141ED76295443F /* ProjectStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectStore.swift; sourceTree = ""; }; + 73AED5B4DD104E0170E7B995 /* CompletionPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionPopover.swift; sourceTree = ""; }; + 772CFF753C6F3A5ACBE3ADB3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 772FA6F31FCD4165D27B03B8 /* GitPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitPanel.swift; sourceTree = ""; }; + 7F9E7D37468DC86AA0FE9EA4 /* EditorKeyboardToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorKeyboardToolbar.swift; sourceTree = ""; }; + 802D34EBD792211122BFFAD1 /* FileOperationSheets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOperationSheets.swift; sourceTree = ""; }; + 8353DAEA46503C5282B7B130 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; + 86C371B51B38EBEF37CC9187 /* UnsavedChangesGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsavedChangesGuard.swift; sourceTree = ""; }; + 89E2479AE0F335916A182D21 /* EditorTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBar.swift; sourceTree = ""; }; + 949E76D0BDDF5F0CCBA337B2 /* EditorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorState.swift; sourceTree = ""; }; + 99F35A0DBED65BE54BE0E9DB /* ToastSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastSystem.swift; sourceTree = ""; }; + A099AC677DB833A24E5B11F6 /* PadXcodeApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadXcodeApp.swift; sourceTree = ""; }; + A3A46BFD2F8AEFA0A78AE0D6 /* BuildService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildService.swift; sourceTree = ""; }; + A7A097BE571671DFC91687F5 /* BuildToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildToolbar.swift; sourceTree = ""; }; + B0065F9C4376D6BC348C0314 /* GutterAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutterAnnotationView.swift; sourceTree = ""; }; + B107E8DEB3E6DEF57F78FF84 /* RunestoneEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunestoneEditorView.swift; sourceTree = ""; }; + B2A7F6CB2C7736B088EAE7A3 /* GitStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitStore.swift; sourceTree = ""; }; + BA68C429721ACAC7F5762A0E /* GitService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitService.swift; sourceTree = ""; }; + C6131F64D27F3F414498EE33 /* PadXcodeTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadXcodeTheme.swift; sourceTree = ""; }; + C7807AF0FAB473BDE4EEE42D /* CodeEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditorView.swift; sourceTree = ""; }; + C8C6CF7ECB54685991BF2756 /* JumpToLineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JumpToLineView.swift; sourceTree = ""; }; + CB0ADB757F3F9FD0EEDE0792 /* LocalGitConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalGitConfiguration.swift; sourceTree = ""; }; + CD30F2AC447C8CDFD18762C4 /* PadXcode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PadXcode.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D898CA50CF77E9D99DF46861 /* WorkingCopyGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkingCopyGuard.swift; sourceTree = ""; }; + DD3760369B89F6A8D0494672 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + E9020C062BD9713290C885D1 /* BuildPreflightChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildPreflightChecker.swift; sourceTree = ""; }; + F01699A9B4132700BCED77AF /* DaemonHealthMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaemonHealthMonitor.swift; sourceTree = ""; }; + F4C0BB28FA3672ACCF8B1F18 /* FileOperationValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOperationValidator.swift; sourceTree = ""; }; + F6151BCED355A7DB45874354 /* PreflightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreflightView.swift; sourceTree = ""; }; + FC68A23144FCD4E447D67C01 /* GitCallbackHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitCallbackHandler.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C8933B5190DE7E313A882824 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 86695EF4C142827F9A7E5262 /* Runestone in Frameworks */, + E572F4D5B59D51F7026DDBC1 /* TreeSitterSwiftRunestone in Frameworks */, + 7C5CED6B1BB53D0BEF24A071 /* TreeSitterJSONRunestone in Frameworks */, + CE20F98B2486E1E153DDE6CA /* TreeSitterMarkdownRunestone in Frameworks */, + 338405877BC9B112C1A5583B /* TreeSitterBashRunestone in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 001E47676F4936081E78102A /* Navigator */ = { + isa = PBXGroup; + children = ( + 802D34EBD792211122BFFAD1 /* FileOperationSheets.swift */, + 2FE12DC24C78ACBF394E7F0B /* GitAwareFileNavigatorView.swift */, + ); + path = Navigator; + sourceTree = ""; + }; + 08BDBB6AA3128068C8DE687F /* Theme */ = { + isa = PBXGroup; + children = ( + 486323C78B9C82E619CA733D /* LanguageDetector.swift */, + C6131F64D27F3F414498EE33 /* PadXcodeTheme.swift */, + ); + path = Theme; + sourceTree = ""; + }; + 0EDA2B92E8DD9037C9320F0D /* Settings */ = { + isa = PBXGroup; + children = ( + 6D5840FC45E969239D7B2B34 /* SettingsView.swift */, + ); + path = Settings; + sourceTree = ""; + }; + 128CAC9F54E6549519CD99E5 /* Layout */ = { + isa = PBXGroup; + children = ( + A7A097BE571671DFC91687F5 /* BuildToolbar.swift */, + 772CFF753C6F3A5ACBE3ADB3 /* ContentView.swift */, + 54A1BF00F860C37D9DC8C613 /* EnhancedConsoleView.swift */, + ); + path = Layout; + sourceTree = ""; + }; + 1B791F655CA20ED5CB9A21AD /* Editor */ = { + isa = PBXGroup; + children = ( + C7807AF0FAB473BDE4EEE42D /* CodeEditorView.swift */, + 1197CE2D262CAB36A4EB08B1 /* CompletionController.swift */, + 7F9E7D37468DC86AA0FE9EA4 /* EditorKeyboardToolbar.swift */, + 949E76D0BDDF5F0CCBA337B2 /* EditorState.swift */, + 89E2479AE0F335916A182D21 /* EditorTabBar.swift */, + 24AEDD9354F0B2D5F9529B63 /* FindAcrossFilesView.swift */, + 3C9F83681CE5ECA07416A9FA /* FindInFileView.swift */, + B0065F9C4376D6BC348C0314 /* GutterAnnotationView.swift */, + 403212EB7771BE3BDA8D2824 /* HardwareKeyboardCommandHandler.swift */, + C8C6CF7ECB54685991BF2756 /* JumpToLineView.swift */, + B107E8DEB3E6DEF57F78FF84 /* RunestoneEditorView.swift */, + ); + path = Editor; + sourceTree = ""; + }; + 2AE5213C0EFCB72C98B383F2 /* Build */ = { + isa = PBXGroup; + children = ( + 3A5247AA5BF912B8922DCB52 /* BuildHistoryStore.swift */, + E9020C062BD9713290C885D1 /* BuildPreflightChecker.swift */, + F6151BCED355A7DB45874354 /* PreflightView.swift */, + ); + path = Build; + sourceTree = ""; + }; + 2E1F9A95CFA51A3DBA365597 /* Diagnostics */ = { + isa = PBXGroup; + children = ( + 32F592A988E8CAD87E667522 /* BuildDiagnosticParser.swift */, + 73AED5B4DD104E0170E7B995 /* CompletionPopover.swift */, + ); + path = Diagnostics; + sourceTree = ""; + }; + 3A5C1275E8C8FA6CAC87F8BF = { + isa = PBXGroup; + children = ( + A099AC677DB833A24E5B11F6 /* PadXcodeApp.swift */, + DD3760369B89F6A8D0494672 /* SceneDelegate.swift */, + 2AE5213C0EFCB72C98B383F2 /* Build */, + AB8AEEB4DCD48BCA17671113 /* Config */, + 2E1F9A95CFA51A3DBA365597 /* Diagnostics */, + 1B791F655CA20ED5CB9A21AD /* Editor */, + D48F9FA03FA4044C6E37181D /* Git */, + 128CAC9F54E6549519CD99E5 /* Layout */, + 001E47676F4936081E78102A /* Navigator */, + A0B252F8A0DC574C1A8C88D8 /* Network */, + AB595EB21B8B99DB87CBDEB0 /* Onboarding */, + 0EDA2B92E8DD9037C9320F0D /* Settings */, + 726EA157E7D56E1537B3979F /* Shared */, + 08BDBB6AA3128068C8DE687F /* Theme */, + C4EA40130D7B0405BDC42062 /* UI */, + F81B719B69C2BD4C9E22E31D /* Validation */, + CF71EFCDBAAFCDF11EA83B3A /* Products */, + ); + sourceTree = ""; + }; + 726EA157E7D56E1537B3979F /* Shared */ = { + isa = PBXGroup; + children = ( + 040994AC7B1C480C01312210 /* SharedModels.swift */, + ); + path = Shared; + sourceTree = ""; + }; + A0B252F8A0DC574C1A8C88D8 /* Network */ = { + isa = PBXGroup; + children = ( + A3A46BFD2F8AEFA0A78AE0D6 /* BuildService.swift */, + F01699A9B4132700BCED77AF /* DaemonHealthMonitor.swift */, + 1F973FFA66BA97FB15693831 /* FileSyncPipeline.swift */, + 4C80ED0B1C6FB0AE8B7D525F /* LSPClient.swift */, + ); + path = Network; + sourceTree = ""; + }; + AB595EB21B8B99DB87CBDEB0 /* Onboarding */ = { + isa = PBXGroup; + children = ( + 8353DAEA46503C5282B7B130 /* OnboardingView.swift */, + ); + path = Onboarding; + sourceTree = ""; + }; + AB8AEEB4DCD48BCA17671113 /* Config */ = { + isa = PBXGroup; + children = ( + 61F2B8E63953C26B44921E94 /* DaemonConfiguration.swift */, + CB0ADB757F3F9FD0EEDE0792 /* LocalGitConfiguration.swift */, + 72C6481C52141ED76295443F /* ProjectStore.swift */, + ); + path = Config; + sourceTree = ""; + }; + C4EA40130D7B0405BDC42062 /* UI */ = { + isa = PBXGroup; + children = ( + 99F35A0DBED65BE54BE0E9DB /* ToastSystem.swift */, + ); + path = UI; + sourceTree = ""; + }; + CF71EFCDBAAFCDF11EA83B3A /* Products */ = { + isa = PBXGroup; + children = ( + CD30F2AC447C8CDFD18762C4 /* PadXcode.app */, + ); + name = Products; + sourceTree = ""; + }; + D48F9FA03FA4044C6E37181D /* Git */ = { + isa = PBXGroup; + children = ( + FC68A23144FCD4E447D67C01 /* GitCallbackHandler.swift */, + 772FA6F31FCD4165D27B03B8 /* GitPanel.swift */, + BA68C429721ACAC7F5762A0E /* GitService.swift */, + B2A7F6CB2C7736B088EAE7A3 /* GitStore.swift */, + ); + path = Git; + sourceTree = ""; + }; + F81B719B69C2BD4C9E22E31D /* Validation */ = { + isa = PBXGroup; + children = ( + F4C0BB28FA3672ACCF8B1F18 /* FileOperationValidator.swift */, + 03BBB5694C44453C03F81564 /* RetryPolicy.swift */, + 86C371B51B38EBEF37CC9187 /* UnsavedChangesGuard.swift */, + D898CA50CF77E9D99DF46861 /* WorkingCopyGuard.swift */, + ); + path = Validation; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F38187C3AFEBE6E51DF9A567 /* PadXcode */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43162ECBE691EFCA45732305 /* Build configuration list for PBXNativeTarget "PadXcode" */; + buildPhases = ( + 27ED70F5754EC0C4E7E5818E /* SwiftLint (optional) */, + 59C0A30DFCDFA7528EEAC6AD /* Sources */, + C8933B5190DE7E313A882824 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PadXcode; + packageProductDependencies = ( + 3120FC610B14411603CDF871 /* Runestone */, + 8410B0D892289F9738E1C07D /* TreeSitterSwiftRunestone */, + E47877F105A71A00A5028F8F /* TreeSitterJSONRunestone */, + BC622C188F819128EFAD41FC /* TreeSitterMarkdownRunestone */, + 956726714707A9B39225C5A6 /* TreeSitterBashRunestone */, + ); + productName = PadXcode; + productReference = CD30F2AC447C8CDFD18762C4 /* PadXcode.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 45A4DCC266B38C5780808102 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1500; + TargetAttributes = { + F38187C3AFEBE6E51DF9A567 = { + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 5F6603DC2B6EF3C225AAEE56 /* Build configuration list for PBXProject "PadXcode" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 3A5C1275E8C8FA6CAC87F8BF; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 125671CA66EE4DCA07AFEA40 /* XCRemoteSwiftPackageReference "Runestone" */, + 081922004D494D01B3E1BBFD /* XCRemoteSwiftPackageReference "TreeSitterLanguages" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = CF71EFCDBAAFCDF11EA83B3A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F38187C3AFEBE6E51DF9A567 /* PadXcode */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 27ED70F5754EC0C4E7E5818E /* SwiftLint (optional) */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "SwiftLint (optional)"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint > /dev/null; then\n swiftlint\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 59C0A30DFCDFA7528EEAC6AD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C6BACCBCDECFAF935E59078A /* BuildDiagnosticParser.swift in Sources */, + E47EAEA1E890FF698EDAD30C /* BuildHistoryStore.swift in Sources */, + E31CC0E6168F30611E2EA728 /* BuildPreflightChecker.swift in Sources */, + EF158635704532F420B9A4EA /* BuildService.swift in Sources */, + BC73C1C7C7900157D9771F0B /* BuildToolbar.swift in Sources */, + 4BB903BFD791318133D6A069 /* CodeEditorView.swift in Sources */, + 6926E7BA074643381A03EBF2 /* CompletionController.swift in Sources */, + 5DEB0C4381E2506328432D78 /* CompletionPopover.swift in Sources */, + 152AF7634AD26B9DB0C0C0E2 /* ContentView.swift in Sources */, + 573D6A16ACD1B78394565880 /* DaemonConfiguration.swift in Sources */, + A745764D69CD7065491307FD /* DaemonHealthMonitor.swift in Sources */, + DE1D50F19A7356757663936E /* EditorKeyboardToolbar.swift in Sources */, + 93BBC37FBE9490A3863319D9 /* EditorState.swift in Sources */, + 4457404AF277B3BF0FD0EAAD /* EditorTabBar.swift in Sources */, + 90C3D4D8698EE2901CDF6487 /* EnhancedConsoleView.swift in Sources */, + CA9FB2823282C67850985CDA /* FileOperationSheets.swift in Sources */, + 1733074A4A501358F74BA56C /* FileOperationValidator.swift in Sources */, + 2D078ABFBD23CEE45DB030D7 /* FileSyncPipeline.swift in Sources */, + 2A5AC5BE78730EFB7EB5D13C /* FindAcrossFilesView.swift in Sources */, + 1958662EADCBB69352191A3E /* FindInFileView.swift in Sources */, + F055206CFF5CC3DD2FF589CE /* GitAwareFileNavigatorView.swift in Sources */, + 9E5911ECB267D71446335BAC /* GitCallbackHandler.swift in Sources */, + 33C2A48DAF7FC9D16CDF640F /* GitPanel.swift in Sources */, + AB67A1DBA578A7364FE7E0E2 /* GitService.swift in Sources */, + 371D5862E0FC9D88AE10E0B1 /* GitStore.swift in Sources */, + 88EDF52AD32D156D991366DD /* GutterAnnotationView.swift in Sources */, + FD74914AEA0642D536E3505F /* HardwareKeyboardCommandHandler.swift in Sources */, + CB9FEAD7791E0F024861295A /* JumpToLineView.swift in Sources */, + E052E548B69DEAE08B676FBC /* LSPClient.swift in Sources */, + 083FF8D3F7D7C404292CC778 /* LanguageDetector.swift in Sources */, + BCC8A449CD3D69A848DBFF51 /* LocalGitConfiguration.swift in Sources */, + 1B14F98EC8A4E5141A15BF12 /* OnboardingView.swift in Sources */, + A2FB9D4EEB36892347BEF121 /* PadXcodeApp.swift in Sources */, + 36907E0F5E04B4AC0B4A02BD /* PadXcodeTheme.swift in Sources */, + 23CD88A842BF5B264AF834DF /* PreflightView.swift in Sources */, + ACB9ACF2E46075B1DCD0FF9F /* ProjectStore.swift in Sources */, + C6139C1D648C21FE025EAAF8 /* RetryPolicy.swift in Sources */, + F7503FE6FDAF26BCFF4E45D7 /* RunestoneEditorView.swift in Sources */, + C03314EAF4EC702A21E41D17 /* SceneDelegate.swift in Sources */, + 81115284C29C9884270CCF68 /* SettingsView.swift in Sources */, + 651C31BD6316AD08A89759C4 /* SharedModels.swift in Sources */, + 403E2F72B0BABD711A511638 /* ToastSystem.swift in Sources */, + 57F8F70B263E6097FF0E60C8 /* UnsavedChangesGuard.swift in Sources */, + F5E24A92C0FC384F7BE75F19 /* WorkingCopyGuard.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 5D971FF1F5130913EE92CE3F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = PadXcode.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = E9C9AGS9K6; + INFOPLIST_FILE = Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 80CF6787C32190E0625B3CCD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = PadXcode.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = E9C9AGS9K6; + INFOPLIST_FILE = Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AE4DDA67DA1A8F0198344D85 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MARKETING_VERSION = 1.0.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = ca.dallasgroot.PadXcode; + PRODUCT_NAME = PadXcode; + SDKROOT = iphoneos; + SUPPORTS_MAC_DESIGNED_FOR_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.9; + TARGETED_DEVICE_FAMILY = 2; + }; + name = Debug; + }; + FF6085DBCE1A799A09A55D5A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MARKETING_VERSION = 1.0.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = ca.dallasgroot.PadXcode; + PRODUCT_NAME = PadXcode; + SDKROOT = iphoneos; + SUPPORTS_MAC_DESIGNED_FOR_IPAD = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.9; + TARGETED_DEVICE_FAMILY = 2; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 43162ECBE691EFCA45732305 /* Build configuration list for PBXNativeTarget "PadXcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 80CF6787C32190E0625B3CCD /* Debug */, + 5D971FF1F5130913EE92CE3F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 5F6603DC2B6EF3C225AAEE56 /* Build configuration list for PBXProject "PadXcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AE4DDA67DA1A8F0198344D85 /* Debug */, + FF6085DBCE1A799A09A55D5A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 081922004D494D01B3E1BBFD /* XCRemoteSwiftPackageReference "TreeSitterLanguages" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/simonbs/TreeSitterLanguages.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.1.10; + }; + }; + 125671CA66EE4DCA07AFEA40 /* XCRemoteSwiftPackageReference "Runestone" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/simonbs/Runestone.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.5.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 3120FC610B14411603CDF871 /* Runestone */ = { + isa = XCSwiftPackageProductDependency; + package = 125671CA66EE4DCA07AFEA40 /* XCRemoteSwiftPackageReference "Runestone" */; + productName = Runestone; + }; + 8410B0D892289F9738E1C07D /* TreeSitterSwiftRunestone */ = { + isa = XCSwiftPackageProductDependency; + package = 081922004D494D01B3E1BBFD /* XCRemoteSwiftPackageReference "TreeSitterLanguages" */; + productName = TreeSitterSwiftRunestone; + }; + 956726714707A9B39225C5A6 /* TreeSitterBashRunestone */ = { + isa = XCSwiftPackageProductDependency; + package = 081922004D494D01B3E1BBFD /* XCRemoteSwiftPackageReference "TreeSitterLanguages" */; + productName = TreeSitterBashRunestone; + }; + BC622C188F819128EFAD41FC /* TreeSitterMarkdownRunestone */ = { + isa = XCSwiftPackageProductDependency; + package = 081922004D494D01B3E1BBFD /* XCRemoteSwiftPackageReference "TreeSitterLanguages" */; + productName = TreeSitterMarkdownRunestone; + }; + E47877F105A71A00A5028F8F /* TreeSitterJSONRunestone */ = { + isa = XCSwiftPackageProductDependency; + package = 081922004D494D01B3E1BBFD /* XCRemoteSwiftPackageReference "TreeSitterLanguages" */; + productName = TreeSitterJSONRunestone; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 45A4DCC266B38C5780808102 /* Project object */; +} diff --git a/PadXcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PadXcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/PadXcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/PadXcode.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PadXcode.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..8cb22e2 --- /dev/null +++ b/PadXcode.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,33 @@ +{ + "originHash" : "9c65c0b0febfdd26f77e3cd309508b657686cd4c674274676406c6252c45c069", + "pins" : [ + { + "identity" : "runestone", + "kind" : "remoteSourceControl", + "location" : "https://github.com/simonbs/Runestone.git", + "state" : { + "revision" : "592434a103a4d1ab83e14f87ac6eef569dd7a99d", + "version" : "0.5.2" + } + }, + { + "identity" : "tree-sitter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tree-sitter/tree-sitter", + "state" : { + "revision" : "98be227227af10cc7a269cb3ffb23686c0610b17", + "version" : "0.20.9" + } + }, + { + "identity" : "treesitterlanguages", + "kind" : "remoteSourceControl", + "location" : "https://github.com/simonbs/TreeSitterLanguages.git", + "state" : { + "revision" : "15cf3a9ec3ab95e0d058b7df9f35619123c9e02d", + "version" : "0.1.10" + } + } + ], + "version" : 3 +} diff --git a/PadXcode.xcodeproj/project.xcworkspace/xcuserdata/dallasgroot.xcuserdatad/UserInterfaceState.xcuserstate b/PadXcode.xcodeproj/project.xcworkspace/xcuserdata/dallasgroot.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..5a08216581e19ba4ad20fd5ebe053aa5194ad9c4 GIT binary patch literal 25620 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 Ol3-|k8TvCz`o949QSm4M literal 0 HcmV?d00001 diff --git a/PadXcode.xcodeproj/xcuserdata/dallasgroot.xcuserdatad/xcschemes/xcschememanagement.plist b/PadXcode.xcodeproj/xcuserdata/dallasgroot.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..4053001 --- /dev/null +++ b/PadXcode.xcodeproj/xcuserdata/dallasgroot.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + PadXcode.xcscheme_^#shared#^_ + + orderHint + 0 + + + + From 425c6b8024f930d7a3471b35b77d46411e08d55f Mon Sep 17 00:00:00 2001 From: Dallas Groot Date: Sun, 12 Apr 2026 09:47:09 -0700 Subject: [PATCH 2/3] first successful build --- .DS_Store | Bin 6148 -> 8196 bytes Config/LocalGitConfiguration.swift | 6 +- Editor/CodeEditorView.swift | 12 + Editor/CompletionController.swift | 22 +- Editor/EditorKeyboardToolbar.swift | 258 ++++++++++--- Editor/EditorState.swift | 5 + Editor/GutterAnnotationView.swift | 26 +- Editor/HardwareKeyboardCommandHandler.swift | 78 +++- Editor/RunestoneEditorView.swift | 113 ++++-- Git/GitPanel.swift | 8 +- Info.plist | 21 +- Layout/ContentView.swift | 4 +- Layout/EnhancedConsoleView.swift | 8 +- Onboarding/OnboardingView.swift | 2 +- PadXcode.xcodeproj/project.pbxproj | 2 + .../UserInterfaceState.xcuserstate | Bin 25620 -> 18929 bytes PadXcodeApp.swift | 21 +- Settings/SettingsView.swift | 304 +++++++++++----- Shared/SharedModels.swift | 23 +- Theme/LanguageDetector.swift | 1 + Theme/PadXcodeTheme.swift | 339 ++++++++++++------ UI/ToastSystem.swift | 2 +- project.yml | 1 + 23 files changed, 926 insertions(+), 330 deletions(-) diff --git a/.DS_Store b/.DS_Store index b0365f69dbf3d7c6a003bf6106e5c4eb10100f34..8467506d6fea0333ebd714d072bf387a7aad7229 100644 GIT binary patch literal 8196 zcmeHM%Wl&^6g`tZNRtf+6?KE;A0QHckcL<&5`>BXVo8&@X(6qvG@)YWefRtcJAQ+G zzkod}IQPy7+2eExMdFS$a~%8J$DA44x()!>TTLGWdjOj3f_K{-)-<^;z1AAx%mbpM zJun^2^T~Lgk84~zVG5W6rhqA63YY@_h5~$NOKaZn-uG8+GzCn7|55>YJ|ygd-GGHh zeROcp5`ehL)7p5VBSUp=^B>OzU(yg9EYLML_u79PDhoHvIHkzJ@z zoJOZzqI9^fM;lE6Q=qPZ)b3m8VTBXSEA_kFNT1oWpW4)P8&8nq3`bnY~ZtoiV-Snc2?QtWf* zJtuZ(QT`$&yOgR{Ebe1O|9%(si84#SB7Qfm%~sT|cxlp$tc@r|o#S0DN>}*X z0;a$pRKRWaNBtfHgxb0mN^0$d{T;hB;T0a$goBpjL@mdOzyC1gaYCxtbORP1d4|nC N1gH!)m;!%QfnNiyL#+S+ literal 6148 zcmeHKOG*Pl5UtW#47v%fT=ofs+@KT2y^xJdO-2++m_ZO+_SqEi0G=j0UwtAm&LX%F zkt*nZHP!X{^I-ZzL_B?L$3!C{s!;@4oB@;YVAFvoS3uT27SvHoH*`i%10DUvkj(vr zF6bKbzNGE>Z~UGfu&Vpz?Rd86R=q7^{k$)4)iuq0+0Bt(SL^qqm)Ez+{@nJ*-2QHB zzML^+SSSz*gaV;JD4+`9%oeMU4WkYPLV-}=Qvul@5{h7UEQY#upwcG*P_EHx@Z~Kb zoaC4tiy>NIY*e68*`F9}bi|YAWyfM@bYdMD{rJe6#|!J|h$l^_xcQn6QHKJ%3LIKs zBUn#&L7AI$ILb*Z_le{7h4E~o(06t_NIn_pM bPvRplI~GIfMdC^a#zjB{i7FKM1qD6;3Fbfu diff --git a/Config/LocalGitConfiguration.swift b/Config/LocalGitConfiguration.swift index 6287d9f..cdb5037 100644 --- a/Config/LocalGitConfiguration.swift +++ b/Config/LocalGitConfiguration.swift @@ -78,9 +78,9 @@ git push -u origin main } private struct SetupStep: View { - let step: String; let title: String; let body: String + let step: String; let title: String; let detail: String init(_ step: String, title: String, body: String) { - self.step = step; self.title = title; self.body = body + self.step = step; self.title = title; self.detail = body } var body: some View { HStack(alignment: .top, spacing: 12) { @@ -89,7 +89,7 @@ private struct SetupStep: View { .background(Color.accentColor, in: Circle()) VStack(alignment: .leading, spacing: 4) { Text(title).font(.headline) - Text(body).font(.subheadline).foregroundStyle(.secondary) + Text(detail).font(.subheadline).foregroundStyle(.secondary) } } } diff --git a/Editor/CodeEditorView.swift b/Editor/CodeEditorView.swift index 93482fb..ce3b708 100644 --- a/Editor/CodeEditorView.swift +++ b/Editor/CodeEditorView.swift @@ -91,6 +91,18 @@ struct CodeEditorView: View { .onReceive(NotificationCenter.default.publisher(for: .navigateToRange)) { note in if let range = note.userInfo?["range"] as? NSRange { navigate(to: range) } } + .onReceive(NotificationCenter.default.publisher(for: .editorFontSizeChange)) { note in + if let delta = note.userInfo?["delta"] as? Double { + let newSize = max(8, min(32, editorState.fontSizeStored + delta)) + editorState.fontSizeStored = newSize + } + } + .onReceive(NotificationCenter.default.publisher(for: .editorFindNext)) { _ in + keyboardBridge.triggerFindNext() + } + .onReceive(NotificationCenter.default.publisher(for: .editorFindPrev)) { _ in + keyboardBridge.triggerFindPrev() + } } private func jumpTo(_ line: Int) { diff --git a/Editor/CompletionController.swift b/Editor/CompletionController.swift index b7357a7..e1b2483 100644 --- a/Editor/CompletionController.swift +++ b/Editor/CompletionController.swift @@ -18,7 +18,8 @@ final class CompletionController: ObservableObject { func attach(to tv: Runestone.TextView) { self.textView = tv } func textDidChange(in tv: Runestone.TextView, filePath: String) { - guard let sel = tv.selectedRange, sel.location > 0 else { dismiss(); return } + let sel = tv.selectedRange + guard sel.location > 0 else { dismiss(); return } let text = tv.text as NSString let c = text.character(at: sel.location - 1) let shouldTrigger = autoTriggers.contains(Character(Unicode.Scalar(c)!)) @@ -42,24 +43,29 @@ final class CompletionController: ObservableObject { func dismiss() { debounceTask?.cancel(); items = []; isVisible = false } func insert(_ item: CompletionItem, into tv: Runestone.TextView) { - guard let sel = tv.selectedRange else { dismiss(); return } + let sel = tv.selectedRange let text = tv.text as NSString let wordRange = text.rangeOfCurrentWord(at: sel.location) let insertText = item.insertText ?? item.label tv.replace(wordRange, withText: insertText) if insertText.hasSuffix("()") { - let loc = (tv.selectedRange?.location ?? 1) - 1 + let loc = tv.selectedRange.location > 0 ? tv.selectedRange.location - 1 : 0 tv.selectedRange = NSRange(location: loc, length: 0) } dismiss() } private func requestCompletions(tv: Runestone.TextView, filePath: String) { - guard let sel = tv.selectedRange, - let loc = tv.textLocation(at: sel.location) else { return } - if let rect = tv.caretRect(for: sel.location) { - anchorRect = tv.convert(rect, to: nil) - } + let sel = tv.selectedRange + guard let loc = tv.textLocation(at: sel.location) else { return } + // caretRect(for:) on Runestone.TextView takes UITextPosition, not Int. + // Approximate anchor position from font metrics for the completion popover. + let font = tv.theme.font + let lineHeight = ceil(font.lineHeight * tv.lineHeightMultiplier) + let approxY = CGFloat(loc.lineNumber) * lineHeight + + tv.textContainerInset.top + - tv.contentOffset.y + anchorRect = CGRect(x: 0, y: approxY, width: 0, height: lineHeight) lastId += 1; let id = lastId Task { [weak self] in guard let self else { return } diff --git a/Editor/EditorKeyboardToolbar.swift b/Editor/EditorKeyboardToolbar.swift index d84c77c..3aafdc2 100644 --- a/Editor/EditorKeyboardToolbar.swift +++ b/Editor/EditorKeyboardToolbar.swift @@ -1,31 +1,50 @@ import SwiftUI +import UIKit import Runestone +// MARK: - Keyboard Bridge + final class EditorKeyboardBridge: ObservableObject { weak var activeTextView: Runestone.TextView? + + // Called by CodeEditorView when ⌘G / ⌘⇧G hardware shortcut fires + var onFindNext: (() -> Void)? + var onFindPrev: (() -> Void)? + + func triggerFindNext() { onFindNext?() } + func triggerFindPrev() { onFindPrev?() } } +// MARK: - Toolbar + struct EditorKeyboardToolbar: View { @ObservedObject var bridge: EditorKeyboardBridge @State private var showFindBar = false @State private var searchQuery = "" - @State private var isRegex = false + @State private var lastMatchRange: NSRange = NSRange(location: NSNotFound, length: 0) var body: some View { VStack(spacing: 0) { if showFindBar { - InlineFindBar(query: $searchQuery, isRegex: $isRegex, - onNext: findNext, onPrev: findPrev, - onDismiss: { withAnimation { showFindBar = false } }) - .transition(.move(edge: .top).combined(with: .opacity)) + InlineFindBar( + query: $searchQuery, + onNext: findNext, + onPrev: findPrev, + onDismiss: { + withAnimation { showFindBar = false } + searchQuery = "" + lastMatchRange = NSRange(location: NSNotFound, length: 0) + } + ) + .transition(.move(edge: .top).combined(with: .opacity)) Divider() } ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 4) { - KeyToolbarButton(icon: "increase.indent", label: "Indent") { bridge.activeTextView?.shiftRight() } - KeyToolbarButton(icon: "decrease.indent", label: "Unindent") { bridge.activeTextView?.shiftLeft() } + KeyToolbarButton(icon: "increase.indent", label: "Indent") { indent(increase: true) } + KeyToolbarButton(icon: "decrease.indent", label: "Unindent") { indent(increase: false) } KeyToolbarDivider() - KeyToolbarButton(icon: "text.badge.slash", label: "Comment") { toggleComment() } + KeyToolbarButton(icon: "text.badge.slash", label: "Comment") { toggleComment() } KeyToolbarDivider() KeyToolbarText("(") { insertPair("(", ")") } KeyToolbarText("[") { insertPair("[", "]") } @@ -38,86 +57,233 @@ struct EditorKeyboardToolbar: View { KeyToolbarButton(icon: "arrow.left", label: "Left") { moveCursor(-1) } KeyToolbarButton(icon: "arrow.right", label: "Right") { moveCursor(+1) } KeyToolbarDivider() - KeyToolbarButton(icon: "magnifyingglass", label: "Find") { withAnimation { showFindBar.toggle() } } + KeyToolbarButton(icon: "magnifyingglass", label: "Find") { + withAnimation { showFindBar.toggle() } + } Spacer() KeyToolbarButton(icon: "keyboard.chevron.compact.down", label: "Done") { bridge.activeTextView?.resignFirstResponder() } } - .padding(.horizontal, 8).padding(.vertical, 6) + .padding(.horizontal, 8) + .padding(.vertical, 6) } .background(.regularMaterial) } + .onAppear { + bridge.onFindNext = { findNext() } + bridge.onFindPrev = { findPrev() } + } } - private func insert(_ t: String) { bridge.activeTextView?.insertText(t) } - private func insertPair(_ l: String, _ r: String) { bridge.activeTextView?.insertText(l+r); moveCursor(-1) } - private func moveCursor(_ offset: Int) { - guard let tv = bridge.activeTextView, let sel = tv.selectedRange else { return } - tv.selectedRange = NSRange(location: max(0, sel.location + offset), length: 0) + // MARK: - Text Operations + + private func insert(_ text: String) { + bridge.activeTextView?.insertText(text) } - private func toggleComment() { - guard let tv = bridge.activeTextView, let sel = tv.selectedRange else { return } + + private func insertPair(_ open: String, _ close: String) { + bridge.activeTextView?.insertText(open + close) + moveCursor(-1) + } + + private func moveCursor(_ offset: Int) { + guard let tv = bridge.activeTextView else { return } + let sel = tv.selectedRange + let newLocation = max(0, sel.location + offset) + tv.selectedRange = NSRange(location: newLocation, length: 0) + } + + private func indent(increase: Bool) { + guard let tv = bridge.activeTextView else { return } + let sel = tv.selectedRange let text = tv.text as NSString - let lr = text.lineRange(for: sel) - let line = text.substring(with: lr) + let lineRange = text.lineRange(for: sel) + let line = text.substring(with: lineRange) + let modified: String + if increase { + modified = " " + line + } else { + if line.hasPrefix(" ") { + modified = String(line.dropFirst(4)) + } else if line.hasPrefix("\t") { + modified = String(line.dropFirst(1)) + } else { + return + } + } + tv.replace(lineRange, withText: modified) + } + + private func toggleComment() { + guard let tv = bridge.activeTextView else { return } + let sel = tv.selectedRange + let text = tv.text as NSString + let lineRange = text.lineRange(for: sel) + let line = text.substring(with: lineRange) let trimmed = line.trimmingCharacters(in: .whitespaces) let indent = String(line.prefix(while: { $0 == " " || $0 == "\t" })) let newLine: String - if trimmed.hasPrefix("// ") { newLine = line.replacingFirstOccurrence(of: "// ", with: "") } - else if trimmed.hasPrefix("//") { newLine = line.replacingFirstOccurrence(of: "//", with: "") } - else { newLine = indent + "// " + line.dropFirst(indent.count) } - tv.replace(lr, withText: newLine) + if trimmed.hasPrefix("// ") { + newLine = line.replacingFirstOccurrence(of: "// ", with: "") + } else if trimmed.hasPrefix("//") { + newLine = line.replacingFirstOccurrence(of: "//", with: "") + } else { + newLine = indent + "// " + line.dropFirst(indent.count) + } + tv.replace(lineRange, withText: newLine) } + + // MARK: - Find (manual NSString search — no Runestone-specific API) + private func findNext() { guard let tv = bridge.activeTextView, !searchQuery.isEmpty else { return } - tv.selectNextOccurrence(matching: SearchOptions(query: searchQuery)) + let fullText = tv.text as NSString + let totalLength = fullText.length + + // Search from just after the last match, wrapping if needed + let searchStart: Int + if lastMatchRange.location != NSNotFound { + searchStart = min(lastMatchRange.location + lastMatchRange.length, totalLength) + } else { + searchStart = 0 + } + + // Try from searchStart to end + var found = fullText.range(of: searchQuery, + options: .caseInsensitive, + range: NSRange(location: searchStart, + length: totalLength - searchStart)) + + // Wrap around if not found + if found.location == NSNotFound && searchStart > 0 { + found = fullText.range(of: searchQuery, + options: .caseInsensitive, + range: NSRange(location: 0, length: searchStart)) + } + + if found.location != NSNotFound { + lastMatchRange = found + tv.selectedRange = found + tv.scrollRangeToVisible(found) + } } + private func findPrev() { guard let tv = bridge.activeTextView, !searchQuery.isEmpty else { return } - tv.selectPreviousOccurrence(matching: SearchOptions(query: searchQuery)) + let fullText = tv.text as NSString + let totalLength = fullText.length + + // Search backwards from just before the last match + let searchEnd: Int + if lastMatchRange.location != NSNotFound { + searchEnd = lastMatchRange.location + } else { + searchEnd = totalLength + } + + // Try backwards from searchEnd to start + var found = fullText.range(of: searchQuery, + options: [.caseInsensitive, .backwards], + range: NSRange(location: 0, length: searchEnd)) + + // Wrap around to end + if found.location == NSNotFound && searchEnd < totalLength { + found = fullText.range(of: searchQuery, + options: [.caseInsensitive, .backwards], + range: NSRange(location: searchEnd, length: totalLength - searchEnd)) + } + + if found.location != NSNotFound { + lastMatchRange = found + tv.selectedRange = found + tv.scrollRangeToVisible(found) + } } } +// MARK: - Inline Find Bar + struct InlineFindBar: View { - @Binding var query: String; @Binding var isRegex: Bool - var onNext: () -> Void; var onPrev: () -> Void; var onDismiss: () -> Void + @Binding var query: String + var onNext: () -> Void + var onPrev: () -> Void + var onDismiss: () -> Void + var body: some View { HStack(spacing: 8) { - Image(systemName: "magnifyingglass").foregroundStyle(.secondary) - TextField("Find…", text: $query).textFieldStyle(.plain) - .font(.system(.body, design: .monospaced)).autocorrectionDisabled().autocapitalization(.none) - .onSubmit(onNext) - Toggle(isOn: $isRegex) { Image(systemName: "ellipsis.curlybraces") } - .toggleStyle(.button).buttonStyle(.bordered).tint(isRegex ? .accentColor : .secondary) - Button(action: onPrev) { Image(systemName: "chevron.up") }.buttonStyle(.bordered).disabled(query.isEmpty) - Button(action: onNext) { Image(systemName: "chevron.down") }.buttonStyle(.bordered).disabled(query.isEmpty) - Button(action: onDismiss) { Image(systemName: "xmark") }.buttonStyle(.bordered) + Image(systemName: "magnifyingglass") + .foregroundStyle(.secondary) + TextField("Find...", text: $query) + .textFieldStyle(.plain) + .font(.system(.body, design: .monospaced)) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + .onSubmit { onNext() } + Button(action: onPrev) { + Image(systemName: "chevron.up") + } + .buttonStyle(.bordered) + .disabled(query.isEmpty) + Button(action: onNext) { + Image(systemName: "chevron.down") + } + .buttonStyle(.bordered) + .disabled(query.isEmpty) + Button(action: onDismiss) { + Image(systemName: "xmark") + } + .buttonStyle(.bordered) } - .padding(.horizontal, 12).padding(.vertical, 6) + .padding(.horizontal, 12) + .padding(.vertical, 6) } } +// MARK: - Toolbar Sub-components + struct KeyToolbarButton: View { - let icon: String; let label: String; let action: () -> Void + let icon: String + let label: String + let action: () -> Void + var body: some View { - Button(action: action) { Image(systemName: icon).frame(minWidth: 32, minHeight: 32).contentShape(Rectangle()) } - .buttonStyle(.plain).foregroundStyle(.primary).help(label) + Button(action: action) { + Image(systemName: icon) + .frame(minWidth: 32, minHeight: 32) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .foregroundStyle(.primary) + .help(label) } } struct KeyToolbarText: View { - let text: String; let action: () -> Void - init(_ text: String, action: @escaping () -> Void) { self.text = text; self.action = action } + let text: String + let action: () -> Void + + init(_ text: String, action: @escaping () -> Void) { + self.text = text + self.action = action + } + var body: some View { Button(action: action) { - Text(text).font(.system(.callout, design: .monospaced).bold()) - .frame(minWidth: 32, minHeight: 32).contentShape(Rectangle()) + Text(text) + .font(.system(.callout, design: .monospaced).bold()) + .frame(minWidth: 32, minHeight: 32) + .contentShape(Rectangle()) } - .buttonStyle(.plain).foregroundStyle(.primary) + .buttonStyle(.plain) + .foregroundStyle(.primary) } } struct KeyToolbarDivider: View { - var body: some View { Divider().frame(height: 20).padding(.horizontal, 2) } + var body: some View { + Divider() + .frame(height: 20) + .padding(.horizontal, 2) + } } diff --git a/Editor/EditorState.swift b/Editor/EditorState.swift index ec1d2bb..358c48f 100644 --- a/Editor/EditorState.swift +++ b/Editor/EditorState.swift @@ -113,5 +113,10 @@ final class EditorState: ObservableObject { } } + + func markSaved(tabId: UUID) { + guard let idx = tabs.firstIndex(where: { $0.id == tabId }) else { return } + tabs[idx].isDirty = false + } var dirtyTabNames: [String] { tabs.filter { $0.isDirty }.map { $0.fileName } } } diff --git a/Editor/GutterAnnotationView.swift b/Editor/GutterAnnotationView.swift index e6b723b..64038cf 100644 --- a/Editor/GutterAnnotationView.swift +++ b/Editor/GutterAnnotationView.swift @@ -1,10 +1,7 @@ -// GutterAnnotationView.swift -// DiagnosticRange is defined in Shared/SharedModels.swift -// No Identifiable conformance extension needed here. import SwiftUI import Runestone -// MARK: - Annotation Model (local to this file — wraps DiagnosticRange with position) +// MARK: - Annotation Model struct GutterAnnotation: Identifiable { let id = UUID() @@ -69,17 +66,19 @@ struct GutterAnnotationOverlay: View { private func computePositions() { guard let tv = textView else { return } var result: [PositionedAnnotation] = [] - for diag in diagnostics { let ann = GutterAnnotation(line: diag.line, severity: diag.severity, message: diag.message) - guard let range = tv.range(atLine: ann.line, column: 0), - let rect = tv.caretRect(for: range.location) else { continue } - let y = rect.midY - tv.contentOffset.y - if y > 0 && y < tv.bounds.height { - result.append(PositionedAnnotation(annotation: ann, y: y)) - } + // caretRect(for:) on Runestone.TextView takes UITextPosition, not Int. + // Compute Y from font metrics instead — reliable for a code editor + // where all lines have equal height. + let font = tv.theme.font + let lineHeight = ceil(font.lineHeight * tv.lineHeightMultiplier) + let y = CGFloat(ann.line - 1) * lineHeight + + tv.textContainerInset.top + - tv.contentOffset.y + guard y > 0 && y < tv.bounds.height else { continue } + result.append(PositionedAnnotation(annotation: ann, y: y)) } - withAnimation(.easeInOut(duration: 0.15)) { annotations = result } } } @@ -108,7 +107,8 @@ struct DiagnosticPopover: View { } } -// MARK: - Hex colour helper (local) +// MARK: - Hex colour helper (private to this file) + private extension Color { init(hex: String) { var h = hex.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/Editor/HardwareKeyboardCommandHandler.swift b/Editor/HardwareKeyboardCommandHandler.swift index df3fdb0..9811f8c 100644 --- a/Editor/HardwareKeyboardCommandHandler.swift +++ b/Editor/HardwareKeyboardCommandHandler.swift @@ -87,18 +87,33 @@ final class EditorCommandHandler: UIViewController { @objc func cmdUnindent() { textView?.shiftLeft() } @objc func cmdFind() { onFind?() } @objc func cmdFindProject(){ onFindInProject?() } - @objc func cmdFindNext() {} - @objc func cmdFindPrev() {} + @objc func cmdFindNext() { + NotificationCenter.default.post( + name: .editorFindNext, object: nil) + } + @objc func cmdFindPrev() { + NotificationCenter.default.post( + name: .editorFindPrev, object: nil) + } @objc func cmdJumpToLine() { onJumpToLine?() } @objc func cmdBuild() { onBuild?() } @objc func cmdRun() { onRun?() } @objc func cmdNavigator() { onToggleNavigator?() } @objc func cmdConsole() { onToggleConsole?() } - @objc func cmdFontBigger() {} - @objc func cmdFontSmaller(){} + @objc func cmdFontBigger() { + NotificationCenter.default.post( + name: .editorFontSizeChange, object: nil, + userInfo: ["delta": 1.0]) + } + @objc func cmdFontSmaller() { + NotificationCenter.default.post( + name: .editorFontSizeChange, object: nil, + userInfo: ["delta": -1.0]) + } @objc func cmdComment() { - guard let tv = textView, let sel = tv.selectedRange else { return } + guard let tv = textView else { return } + let sel = tv.selectedRange let text = tv.text as NSString; let lr = text.lineRange(for: sel) let line = text.substring(with: lr); let trimmed = line.trimmingCharacters(in: .whitespaces) let indent = String(line.prefix(while: { $0 == " " || $0 == "\t" })) @@ -110,7 +125,8 @@ final class EditorCommandHandler: UIViewController { } @objc func cmdMoveUp() { - guard let tv = textView, let sel = tv.selectedRange else { return } + guard let tv = textView else { return } + let sel = tv.selectedRange let text = tv.text as NSString; let cur = text.lineRange(for: sel) guard cur.location > 0 else { return } let prev = text.lineRange(for: NSRange(location: cur.location - 1, length: 0)) @@ -120,7 +136,8 @@ final class EditorCommandHandler: UIViewController { } @objc func cmdMoveDown() { - guard let tv = textView, let sel = tv.selectedRange else { return } + guard let tv = textView else { return } + let sel = tv.selectedRange let text = tv.text as NSString; let cur = text.lineRange(for: sel) let end = cur.location + cur.length; guard end < text.length else { return } let next = text.lineRange(for: NSRange(location: end, length: 0)) @@ -130,14 +147,16 @@ final class EditorCommandHandler: UIViewController { } @objc func cmdDuplicate() { - guard let tv = textView, let sel = tv.selectedRange else { return } + guard let tv = textView else { return } + let sel = tv.selectedRange let text = tv.text as NSString; let lr = text.lineRange(for: sel) let line = text.substring(with: lr) tv.replace(NSRange(location: lr.location + lr.length, length: 0), withText: line) } @objc func cmdDeleteLine() { - guard let tv = textView, let sel = tv.selectedRange else { return } + guard let tv = textView else { return } + let sel = tv.selectedRange let text = tv.text as NSString tv.replace(text.lineRange(for: sel), withText: "") } @@ -153,3 +172,44 @@ final class EditorCommandHandler: UIViewController { tv.selectedRange = end; tv.scrollRangeToVisible(end) } } + + +// MARK: - SwiftUI wrapper + +struct EditorCommandHandlerView: UIViewControllerRepresentable { + @Binding var textViewRef: Runestone.TextView? + var onSave: (() -> Void)? + var onBuild: (() -> Void)? + var onRun: (() -> Void)? + var onToggleNavigator: (() -> Void)? + var onToggleConsole: (() -> Void)? + var onFind: (() -> Void)? + var onJumpToLine: (() -> Void)? + var onFindInProject: (() -> Void)? + + func makeUIViewController(context: Context) -> EditorCommandHandler { + let vc = EditorCommandHandler() + vc.textView = textViewRef + vc.onSave = onSave + vc.onBuild = onBuild + vc.onRun = onRun + vc.onToggleNavigator = onToggleNavigator + vc.onToggleConsole = onToggleConsole + vc.onFind = onFind + vc.onJumpToLine = onJumpToLine + vc.onFindInProject = onFindInProject + return vc + } + + func updateUIViewController(_ vc: EditorCommandHandler, context: Context) { + vc.textView = textViewRef + vc.onSave = onSave + vc.onBuild = onBuild + vc.onRun = onRun + vc.onToggleNavigator = onToggleNavigator + vc.onToggleConsole = onToggleConsole + vc.onFind = onFind + vc.onJumpToLine = onJumpToLine + vc.onFindInProject = onFindInProject + } +} diff --git a/Editor/RunestoneEditorView.swift b/Editor/RunestoneEditorView.swift index 1657263..76007a1 100644 --- a/Editor/RunestoneEditorView.swift +++ b/Editor/RunestoneEditorView.swift @@ -2,6 +2,16 @@ import SwiftUI import UIKit import Runestone +// MARK: - CharacterPair concrete implementation +// CharacterPair is a protocol in Runestone — no concrete BasicCharacterPair exists. + +private struct EditorCharacterPair: CharacterPair { + let leading: String + let trailing: String +} + +// MARK: - RunestoneEditorView + struct RunestoneEditorView: UIViewRepresentable { @Binding var content: String @@ -20,7 +30,8 @@ struct RunestoneEditorView: UIViewRepresentable { func makeUIView(context: Context) -> Runestone.TextView { let tv = Runestone.TextView() - configureAppearance(tv); configureEditing(tv) + configureAppearance(tv) + configureEditing(tv) tv.editorDelegate = context.coordinator completionController.attach(to: tv) DispatchQueue.main.async { textViewRef = tv } @@ -29,19 +40,30 @@ struct RunestoneEditorView: UIViewRepresentable { } func updateUIView(_ tv: Runestone.TextView, context: Context) { - if tv.text != content && !context.coordinator.isEditing { loadState(into: tv) } + if tv.text != content && !context.coordinator.isEditing { + loadState(into: tv) + } tv.showLineNumbers = showLineNumbers tv.isLineWrappingEnabled = lineWrapping - tv.indentStrategy = indentWidth == 0 ? .tab : .space(length: indentWidth) + tv.indentStrategy = indentWidth == 0 + ? .tab(length: 4) + : .space(length: indentWidth) applyDiagnostics(to: tv) } private func loadState(into tv: Runestone.TextView) { - let lang = LanguageDetector.language(for: filePath) - let capturedContent = content; let capturedTheme = theme + let lang = LanguageDetector.language(for: filePath) + let capturedText = content + let capturedTheme = theme Task.detached(priority: .userInitiated) { - let state = TextViewState(text: capturedContent, theme: capturedTheme, - language: lang.map { TreeSitterLanguageMode(language: $0) }) + // TextViewState init takes TreeSitterLanguage (non-optional). + // Use two separate inits: one with language, one without (plain text). + let state: TextViewState + if let lang = lang { + state = TextViewState(text: capturedText, theme: capturedTheme, language: lang) + } else { + state = TextViewState(text: capturedText, theme: capturedTheme) + } await MainActor.run { tv.setState(state) } } } @@ -49,43 +71,66 @@ struct RunestoneEditorView: UIViewRepresentable { private func configureAppearance(_ tv: Runestone.TextView) { tv.backgroundColor = UIColor(hex: "#1E1E1E") tv.showLineNumbers = showLineNumbers - tv.showSpaces = false; tv.showTabs = false; tv.showLineBreaks = false + tv.showSpaces = false + tv.showTabs = false + tv.showLineBreaks = false tv.isLineWrappingEnabled = lineWrapping - tv.highlightSelectedLine = true - tv.lineHeightMultiplier = 1.4; tv.kern = 0.3 + // highlightSelectedLine was replaced by lineSelectionDisplayType. + // .line highlights the active line in the text area only. + tv.lineSelectionDisplayType = .line + tv.lineHeightMultiplier = 1.4 + tv.kern = 0.3 tv.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 120, right: 0) tv.verticalOverscrollFactor = 0.5 } private func configureEditing(_ tv: Runestone.TextView) { - tv.isEditable = true; tv.autocorrectionType = .no; tv.autocapitalizationType = .none - tv.smartDashesType = .no; tv.smartQuotesType = .no - tv.smartInsertDeleteType = .no; tv.spellCheckingType = .no + tv.isEditable = true + tv.autocorrectionType = .no + tv.autocapitalizationType = .none + tv.smartDashesType = .no + tv.smartQuotesType = .no + tv.smartInsertDeleteType = .no + tv.spellCheckingType = .no tv.characterPairs = [ - BasicCharacterPair(leading: "(", trailing: ")"), - BasicCharacterPair(leading: "[", trailing: "]"), - BasicCharacterPair(leading: "{", trailing: "}"), - BasicCharacterPair(leading: "\"", trailing: "\""), + EditorCharacterPair(leading: "(", trailing: ")"), + EditorCharacterPair(leading: "[", trailing: "]"), + EditorCharacterPair(leading: "{", trailing: "}"), + EditorCharacterPair(leading: "\"", trailing: "\""), ] tv.characterPairTrailingComponentDeletionMode = .immediatelyFollowingLeadingComponent - tv.indentStrategy = indentWidth == 0 ? .tab : .space(length: indentWidth) + tv.indentStrategy = indentWidth == 0 + ? .tab(length: 4) + : .space(length: indentWidth) } private func applyDiagnostics(to tv: Runestone.TextView) { - guard !diagnosticRanges.isEmpty else { tv.setHighlightedRanges([]); return } - let highlights: [HighlightedRange] = diagnosticRanges.compactMap { diag in - guard let range = tv.range(atLine: diag.line, column: diag.column) else { return nil } - let color: UIColor = diag.severity == .error ? UIColor(hex: "#F44747") : UIColor(hex: "#CCA700") - return HighlightedRange(range: range, color: color, cornerRadius: 0, underlineStyle: .single) + guard !diagnosticRanges.isEmpty else { + tv.highlightedRanges = [] + return } - tv.setHighlightedRanges(highlights) + var highlights: [HighlightedRange] = [] + for diag in diagnosticRanges { + // TextLocation is 0-indexed; compiler diagnostics are 1-indexed. + let textLoc = TextLocation(lineNumber: diag.line - 1, column: diag.column - 1) + guard let charOffset = tv.location(at: textLoc) else { continue } + let nsRange = NSRange(location: charOffset, length: max(1, diag.length)) + let color: UIColor = diag.severity == .error + ? UIColor(hex: "#F44747") + : UIColor(hex: "#CCA700") + // HighlightedRange only takes range and color — no cornerRadius/underlineStyle. + highlights.append(HighlightedRange(range: nsRange, color: color)) + } + tv.highlightedRanges = highlights } // MARK: - Coordinator + @MainActor final class Coordinator: NSObject, TextViewDelegate { - var parent: RunestoneEditorView - var isEditing = false + var parent: RunestoneEditorView + var isEditing: Bool = false + init(_ parent: RunestoneEditorView) { self.parent = parent } func textViewDidChange(_ tv: Runestone.TextView) { @@ -97,12 +142,20 @@ struct RunestoneEditorView: UIViewRepresentable { } func textViewDidChangeSelection(_ tv: Runestone.TextView) { - guard let sel = tv.selectedRange, let loc = tv.textLocation(at: sel.location) else { return } - parent.onCursorPositionChange?(loc.lineNumber, loc.column) + let sel = tv.selectedRange + if let loc = tv.textLocation(at: sel.location) { + parent.onCursorPositionChange?(loc.lineNumber + 1, loc.column + 1) + } if !isEditing { parent.completionController.dismiss() } } - func textViewDidBeginEditing(_ tv: Runestone.TextView) { isEditing = true } - func textViewDidEndEditing(_ tv: Runestone.TextView) { isEditing = false; parent.completionController.dismiss() } + func textViewDidBeginEditing(_ tv: Runestone.TextView) { + isEditing = true + } + + func textViewDidEndEditing(_ tv: Runestone.TextView) { + isEditing = false + parent.completionController.dismiss() + } } } diff --git a/Git/GitPanel.swift b/Git/GitPanel.swift index 7cff82b..87d6a82 100644 --- a/Git/GitPanel.swift +++ b/Git/GitPanel.swift @@ -85,7 +85,7 @@ struct GitPanel: View { Image(systemName: branch.isRemote ? "cloud" : "arrow.triangle.branch").font(.caption).foregroundStyle(.secondary) Text(branch.name).font(.system(.body, design: .monospaced)) Spacer() - if branch.name == gitStore.currentBranch { Image(systemName: "checkmark").foregroundStyle(.accentColor) } + if branch.name == gitStore.currentBranch { Image(systemName: "checkmark").foregroundStyle(Color.accentColor) } } }.buttonStyle(.plain) } @@ -213,12 +213,12 @@ struct GitPanel: View { label: { HStack { Image(systemName: branch.isRemote ? "cloud" : "arrow.triangle.branch") - .font(.caption).foregroundStyle(branch.name == gitStore.currentBranch ? .accentColor : .secondary) + .font(.caption).foregroundStyle(branch.name == gitStore.currentBranch ? Color.accentColor : .secondary) Text(branch.name).font(.system(.body, design: .monospaced)) - .foregroundStyle(branch.name == gitStore.currentBranch ? .accentColor : .primary) + .foregroundStyle(branch.name == gitStore.currentBranch ? Color.accentColor : .primary) .bold(branch.name == gitStore.currentBranch) Spacer() - if branch.name == gitStore.currentBranch { Text("current").font(.caption2).foregroundStyle(.accentColor) } + if branch.name == gitStore.currentBranch { Text("current").font(.caption2).foregroundStyle(Color.accentColor) } } } .buttonStyle(.plain) diff --git a/Info.plist b/Info.plist index 2cc1fe0..e1add6b 100644 --- a/Info.plist +++ b/Info.plist @@ -3,14 +3,22 @@ "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + CFBundleExecutable + $(EXECUTABLE_NAME) CFBundleName PadXcode + CFBundleDisplayName + PadXcode CFBundleIdentifier - ca.dallasgroot.PadXcode + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundlePackageType + APPL CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) + CFBundleInfoDictionaryVersion + 6.0 UILaunchStoryboardName LaunchScreen CFBundleURLTypes @@ -47,5 +55,12 @@ UIRequiresFullScreen + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + diff --git a/Layout/ContentView.swift b/Layout/ContentView.swift index 837c0d9..28be0a5 100644 --- a/Layout/ContentView.swift +++ b/Layout/ContentView.swift @@ -38,7 +38,7 @@ struct ContentView: View { _buildService = StateObject(wrappedValue: bs) _syncPipeline = StateObject(wrappedValue: FileSyncPipeline( buildService: bs, gitService: gitService, - gitStore: store, config: config)) + gitStore: store)) } var body: some View { @@ -340,7 +340,7 @@ struct ProjectPickerSheet: View { Text("Scheme: \(p.scheme)").font(.caption2).foregroundStyle(.tertiary) } Spacer() - if p.id == activeProject?.id { Image(systemName:"checkmark.circle.fill").foregroundStyle(.accentColor) } + if p.id == activeProject?.id { Image(systemName:"checkmark.circle.fill").foregroundStyle(Color.accentColor) } } }.buttonStyle(.plain) } diff --git a/Layout/EnhancedConsoleView.swift b/Layout/EnhancedConsoleView.swift index e615937..f11b98d 100644 --- a/Layout/EnhancedConsoleView.swift +++ b/Layout/EnhancedConsoleView.swift @@ -61,12 +61,12 @@ struct EnhancedConsoleView: View { } Spacer() Button { withAnimation { showSearch.toggle(); if !showSearch { searchText = "" } } } - label: { Image(systemName: "magnifyingglass").foregroundStyle(showSearch ? .accentColor : .secondary) } + label: { Image(systemName: "magnifyingglass").foregroundStyle(showSearch ? Color.accentColor : Color.secondary) } .buttonStyle(.plain) Button { UIPasteboard.general.string = filtered.map(\.text).joined(separator: "\n") } label: { Image(systemName: "doc.on.doc").foregroundStyle(.secondary) }.buttonStyle(.plain) Button { isPinned.toggle() } - label: { Image(systemName: isPinned ? "pin.fill" : "pin.slash").foregroundStyle(isPinned ? .accentColor : .secondary) } + label: { Image(systemName: isPinned ? "pin.fill" : "pin.slash").foregroundStyle(isPinned ? Color.accentColor : Color.secondary) } .buttonStyle(.plain) if let clear = onClear { Button(action: clear) { Image(systemName: "trash").foregroundStyle(.secondary) }.buttonStyle(.plain) @@ -122,13 +122,13 @@ struct EnhancedConsoleView: View { private func pillColor(_ mode: FilterMode) -> Color { switch mode { case .errors: return .red; case .warnings: return .orange - case .info: return .blue; case .all: return .accentColor + case .info: return .blue; case .all: return Color.accentColor } } private func gutterColor(_ type: ConsoleLine.LineType) -> Color { switch type { case .error: return .red; case .warning: return .orange - case .success: return .green; case .info: return .accentColor; default: return .clear + case .success: return .green; case .info: return Color.accentColor; default: return .clear } } } diff --git a/Onboarding/OnboardingView.swift b/Onboarding/OnboardingView.swift index 5151c03..eab135b 100644 --- a/Onboarding/OnboardingView.swift +++ b/Onboarding/OnboardingView.swift @@ -67,7 +67,7 @@ struct OnboardingView: View { private var welcomeStep: some View { VStack(spacing: 28) { - Image(systemName: "laptopcomputer.and.ipad").font(.system(size:72,weight:.thin)).foregroundStyle(.accentColor).padding(.top,40) + Image(systemName: "laptopcomputer.and.ipad").font(.system(size:72,weight:.thin)).foregroundStyle(Color.accentColor).padding(.top,40) VStack(spacing:10) { Text("Welcome to PadXcode").font(.largeTitle.bold()).multilineTextAlignment(.center) Text("A native Swift IDE on your iPad, backed by your Mac.").font(.body).foregroundStyle(.secondary).multilineTextAlignment(.center).padding(.horizontal,24) diff --git a/PadXcode.xcodeproj/project.pbxproj b/PadXcode.xcodeproj/project.pbxproj index 8868a81..96e855a 100644 --- a/PadXcode.xcodeproj/project.pbxproj +++ b/PadXcode.xcodeproj/project.pbxproj @@ -439,6 +439,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; CODE_SIGN_ENTITLEMENTS = PadXcode.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_TEAM = E9C9AGS9K6; @@ -457,6 +458,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; CODE_SIGN_ENTITLEMENTS = PadXcode.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_TEAM = E9C9AGS9K6; diff --git a/PadXcode.xcodeproj/project.xcworkspace/xcuserdata/dallasgroot.xcuserdatad/UserInterfaceState.xcuserstate b/PadXcode.xcodeproj/project.xcworkspace/xcuserdata/dallasgroot.xcuserdatad/UserInterfaceState.xcuserstate index 5a08216581e19ba4ad20fd5ebe053aa5194ad9c4..ab5fd689fe15d4beaf4af50fc0f28d87e8c1f614 100644 GIT binary patch delta 10658 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 literal 25620 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 Ol3-|k8TvCz`o949QSm4M diff --git a/PadXcodeApp.swift b/PadXcodeApp.swift index f5d1b0a..7d5ab6e 100644 --- a/PadXcodeApp.swift +++ b/PadXcodeApp.swift @@ -3,22 +3,27 @@ import Foundation @main struct PadXcodeApp: App { - @StateObject private var daemonConfig = DaemonConfiguration() - @StateObject private var projectStore = ProjectStore() - @StateObject private var editorState = EditorState() - @StateObject private var gitStore = GitStore() - @StateObject private var lspClient: LSPClient - private let gitService: GitService + @StateObject private var daemonConfig: DaemonConfiguration + @StateObject private var projectStore: ProjectStore + @StateObject private var editorState: EditorState + @StateObject private var gitStore: GitStore + @StateObject private var lspClient: LSPClient + + private let gitService: GitService private let gitCallbackHandler: GitCallbackHandler init() { let config = DaemonConfiguration() - let apiKey = UserDefaults.standard.string(forKey: "workingCopyAPIKey") ?? "" let store = GitStore() + let apiKey = UserDefaults.standard.string(forKey: "workingCopyAPIKey") ?? "" + _daemonConfig = StateObject(wrappedValue: config) - _lspClient = StateObject(wrappedValue: LSPClient(config: config)) + _projectStore = StateObject(wrappedValue: ProjectStore()) + _editorState = StateObject(wrappedValue: EditorState()) _gitStore = StateObject(wrappedValue: store) + _lspClient = StateObject(wrappedValue: LSPClient(config: config)) + gitService = GitService(apiKey: apiKey) gitCallbackHandler = GitCallbackHandler(store: store) } diff --git a/Settings/SettingsView.swift b/Settings/SettingsView.swift index 12c8bd8..33f302c 100644 --- a/Settings/SettingsView.swift +++ b/Settings/SettingsView.swift @@ -3,8 +3,8 @@ import UIKit struct SettingsView: View { @EnvironmentObject var daemonConfig: DaemonConfiguration - @ObservedObject var editorState: EditorState - @ObservedObject var projectStore: ProjectStore + @ObservedObject var editorState: EditorState + @ObservedObject var projectStore: ProjectStore @State private var pendingHost = "" @State private var pendingPort = "" @@ -12,129 +12,243 @@ struct SettingsView: View { @State private var connStatus: ConnStatus = .untested @State private var showAddProject = false - enum ConnStatus { case untested, testing, success, failure(String) - var label: String { switch self { case .untested: return "Not tested"; case .testing: return "Testing…"; case .success: return "Connected ✓"; case .failure(let m): return "Failed: \(m)" } } - var color: Color { switch self { case .success: return .green; case .failure: return .red; case .testing: return .secondary; default: return .secondary } } - static func ==(a: ConnStatus, b: ConnStatus) -> Bool { - switch (a,b) { case (.success,.success),(.untested,.untested),(.testing,.testing): return true; case (.failure(let x),.failure(let y)): return x==y; default: return false } + // Equatable conformance added so != operator works on line 39 and elsewhere. + enum ConnStatus: Equatable { + case untested, testing, success, failure(String) + + var label: String { + switch self { + case .untested: return "Not tested" + case .testing: return "Testing..." + case .success: return "Connected checkmark" + case .failure(let m): return "Failed: \(m)" + } + } + + var color: Color { + switch self { + case .success: return .green + case .failure: return .red + default: return .secondary + } + } + + // Custom == required because .failure has an associated value. + static func == (a: ConnStatus, b: ConnStatus) -> Bool { + switch (a, b) { + case (.success, .success), (.untested, .untested), (.testing, .testing): return true + case (.failure(let x), .failure(let y)): return x == y + default: return false + } } } var body: some View { NavigationStack { Form { - // ── Daemon ──────────────────────────────────────────────── + + // ── Daemon ────────────────────────────────────────────────── Section { HStack { - Label("Tailscale IP", systemImage:"network"); Spacer() - TextField("100.x.x.x", text:$pendingHost).multilineTextAlignment(.trailing).keyboardType(.numbersAndPunctuation).autocorrectionDisabled().autocapitalization(.none).frame(width:160).onChange(of:pendingHost){_,_ in connStatus = .untested} + Label("Tailscale IP", systemImage: "network") + Spacer() + TextField("100.x.x.x", text: $pendingHost) + .multilineTextAlignment(.trailing) + .keyboardType(.numbersAndPunctuation) + .autocorrectionDisabled() + .autocapitalization(.none) + .frame(width: 160) + .onChange(of: pendingHost) { _, _ in connStatus = .untested } } HStack { - Label("Port", systemImage:"antenna.radiowaves.left.and.right"); Spacer() - TextField("8080", text:$pendingPort).multilineTextAlignment(.trailing).keyboardType(.numberPad).frame(width:80) + Label("Port", systemImage: "antenna.radiowaves.left.and.right") + Spacer() + TextField("8080", text: $pendingPort) + .multilineTextAlignment(.trailing) + .keyboardType(.numberPad) + .frame(width: 80) } - HStack { Text("Status").foregroundStyle(.secondary); Spacer(); Text(connStatus.label).foregroundStyle(connStatus.color).font(.subheadline) } - Button("Test Connection") { Task { await testConnection() } }.disabled(pendingHost.isEmpty) - Button("Save & Apply") { applyConnection() }.buttonStyle(.borderedProminent) + HStack { + Text("Status").foregroundStyle(.secondary) + Spacer() + Text(connStatus.label).foregroundStyle(connStatus.color).font(.subheadline) + } + Button("Test Connection") { Task { await testConnection() } } + .disabled(pendingHost.isEmpty) + Button("Save & Apply") { applyConnection() } + .buttonStyle(.borderedProminent) + // != now works because ConnStatus conforms to Equatable. .disabled(pendingHost.isEmpty || connStatus != .success) } header: { Text("Mac Daemon") } - // ── Projects ────────────────────────────────────────────── + // ── Projects ──────────────────────────────────────────────── Section { ForEach(projectStore.projects) { p in - VStack(alignment:.leading, spacing:2) { + VStack(alignment: .leading, spacing: 2) { Text(p.name).font(.body) Text(p.rootPath).font(.caption).foregroundStyle(.secondary).lineLimit(1).truncationMode(.middle) - Text("Scheme: \(p.scheme) · WC: \(p.workingCopyRepoName.isEmpty ? "not set" : p.workingCopyRepoName)").font(.caption2).foregroundStyle(.tertiary) - }.padding(.vertical,2) - }.onDelete { projectStore.remove(atOffsets:$0) } - Button { showAddProject = true } label: { Label("Add Project", systemImage:"plus.circle") } - } header: { Text("Projects") } footer: { Text("Mac path should be the directory containing project.yml or .xcodeproj") } + Text("Scheme: \(p.scheme) · WC: \(p.workingCopyRepoName.isEmpty ? "not set" : p.workingCopyRepoName)") + .font(.caption2).foregroundStyle(.tertiary) + } + .padding(.vertical, 2) + } + .onDelete { projectStore.remove(atOffsets: $0) } + Button { showAddProject = true } label: { + Label("Add Project", systemImage: "plus.circle") + } + } header: { + Text("Projects") + } footer: { + Text("Mac path should be the directory containing project.yml or .xcodeproj") + } - // ── Editor ──────────────────────────────────────────────── + // ── Editor ────────────────────────────────────────────────── Section("Editor Appearance") { HStack { - Label("Font Size", systemImage:"textformat.size"); Spacer() - Stepper("\(Int(editorState.fontSize))pt", value:$editorState.fontSize, in:8...32, step:1).frame(width:160) + Label("Font Size", systemImage: "textformat.size") + Spacer() + // fontSizeStored is the @AppStorage Double backing fontSize. + // Binding to the computed fontSize property is not possible; + // bind to the stored Double and display with Int cast. + Stepper( + "\(Int(editorState.fontSizeStored))pt", + value: $editorState.fontSizeStored, + in: 8.0...32.0, + step: 1.0 + ) + .frame(width: 160) } - Picker(selection:$editorState.selectedTheme) { - ForEach(EditorThemeOption.allCases) { o in Text(o.displayName).tag(o) } - } label: { Label("Theme", systemImage:"paintbrush") } - Toggle(isOn:$editorState.showLineNumbers) { Label("Line Numbers", systemImage:"list.number") } - Toggle(isOn:$editorState.lineWrapping) { Label("Line Wrapping", systemImage:"text.word.spacing") } + // selectedTheme is a computed property on EditorState — + // cannot use $ directly; use an explicit Binding. + Picker(selection: Binding( + get: { editorState.selectedTheme }, + set: { editorState.selectedTheme = $0 } + )) { + ForEach(EditorThemeOption.allCases) { o in + Text(o.displayName).tag(o) + } + } label: { + Label("Theme", systemImage: "paintbrush") + } + Toggle(isOn: $editorState.showLineNumbers) { Label("Line Numbers", systemImage: "list.number") } + Toggle(isOn: $editorState.lineWrapping) { Label("Line Wrapping", systemImage: "text.word.spacing") } HStack { - Label("Indent Width", systemImage:"arrow.right.to.line"); Spacer() - Picker("", selection:$editorState.indentWidth) { - Text("2 spaces").tag(2); Text("4 spaces").tag(4); Text("Tab").tag(0) - }.labelsHidden().pickerStyle(.menu) + Label("Indent Width", systemImage: "arrow.right.to.line") + Spacer() + Picker("", selection: $editorState.indentWidth) { + Text("2 spaces").tag(2) + Text("4 spaces").tag(4) + Text("Tab").tag(0) + } + .labelsHidden() + .pickerStyle(.menu) } } - // ── Build ───────────────────────────────────────────────── + // ── Build ──────────────────────────────────────────────────── Section("Build") { HStack { - Label("Team ID", systemImage:"person.badge.key"); Spacer() - TextField("XXXXXXXXXX", text:$daemonConfig.developmentTeam).multilineTextAlignment(.trailing).autocorrectionDisabled().autocapitalization(.none).frame(width:120) + Label("Team ID", systemImage: "person.badge.key") + Spacer() + TextField("XXXXXXXXXX", text: $daemonConfig.developmentTeam) + .multilineTextAlignment(.trailing) + .autocorrectionDisabled() + .autocapitalization(.none) + .frame(width: 120) + } + Toggle(isOn: $daemonConfig.allowProvisioningUpdates) { + Label("Allow Provisioning Updates", systemImage: "checkmark.seal") + } + Toggle(isOn: $daemonConfig.buildInRelease) { + Label("Build in Release", systemImage: "bolt") } - Toggle(isOn:$daemonConfig.allowProvisioningUpdates) { Label("Allow Provisioning Updates", systemImage:"checkmark.seal") } - Toggle(isOn:$daemonConfig.buildInRelease) { Label("Build in Release", systemImage:"bolt") } } - // ── Working Copy ────────────────────────────────────────── + // ── Working Copy ───────────────────────────────────────────── Section { HStack { - Label("API Key", systemImage:"key.fill"); Spacer() - SecureField("Paste from Working Copy", text:$wcApiKey).multilineTextAlignment(.trailing).autocorrectionDisabled().autocapitalization(.none).frame(width:200) - .onChange(of:wcApiKey){_,k in UserDefaults.standard.set(k, forKey:"workingCopyAPIKey")} + Label("API Key", systemImage: "key.fill") + Spacer() + SecureField("Paste from Working Copy", text: $wcApiKey) + .multilineTextAlignment(.trailing) + .autocorrectionDisabled() + .autocapitalization(.none) + .frame(width: 200) + .onChange(of: wcApiKey) { _, k in + UserDefaults.standard.set(k, forKey: "workingCopyAPIKey") + } } - let installed = UIApplication.shared.canOpenURL(URL(string:"working-copy://")!) - Label(installed ? "Working Copy detected ✓" : "Working Copy not installed", - systemImage: installed ? "checkmark.circle.fill" : "exclamationmark.triangle") - .foregroundStyle(installed ? .green : .orange).font(.caption) + let installed = UIApplication.shared.canOpenURL(URL(string: "working-copy://")!) + Label( + installed ? "Working Copy detected" : "Working Copy not installed", + systemImage: installed ? "checkmark.circle.fill" : "exclamationmark.triangle" + ) + .foregroundStyle(installed ? .green : .orange) + .font(.caption) Text("For local git over Tailscale, clone in Working Copy using:\nssh://user@100.x.x.x/path/to/repo.git") .font(.caption).foregroundStyle(.secondary) - } header: { Text("Working Copy (Git)") } + } header: { + Text("Working Copy (Git)") + } - // ── LSP ─────────────────────────────────────────────────── + // ── LSP ────────────────────────────────────────────────────── Section { - Toggle(isOn:$daemonConfig.lspEnabled) { Label("sourcekit-lsp Proxy", systemImage:"wand.and.stars") } + Toggle(isOn: $daemonConfig.lspEnabled) { + Label("sourcekit-lsp Proxy", systemImage: "wand.and.stars") + } if daemonConfig.lspEnabled { HStack { - Label("Status", systemImage:"circle.fill").foregroundStyle(daemonConfig.lspConnected ? Color.green : Color.red) + Label("Status", systemImage: "circle.fill") + .foregroundStyle(daemonConfig.lspConnected ? Color.green : Color.red) Spacer() - Text(daemonConfig.lspConnected ? "Running" : "Disconnected").foregroundStyle(.secondary) + Text(daemonConfig.lspConnected ? "Running" : "Disconnected") + .foregroundStyle(.secondary) } } } header: { Text("Language Server (LSP)") } - // ── About ───────────────────────────────────────────────── + // ── About ───────────────────────────────────────────────────── Section("About") { - LabeledContent("Version", value:"1.0.0-alpha") - LabeledContent("Editor Engine", value:"Runestone 0.5.1 / Tree-sitter") - LabeledContent("Protocol", value:"REST + WebSocket") + LabeledContent("Version", value: "1.0.0-alpha") + LabeledContent("Editor Engine", value: "Runestone 0.5.1 / Tree-sitter") + LabeledContent("Protocol", value: "REST + WebSocket") } } - .navigationTitle("Settings").navigationBarTitleDisplayMode(.inline) - .onAppear { pendingHost = daemonConfig.host; pendingPort = String(daemonConfig.port) - wcApiKey = UserDefaults.standard.string(forKey:"workingCopyAPIKey") ?? "" } - .sheet(isPresented:$showAddProject) { AddProjectView(projectStore:projectStore) } + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.inline) + .onAppear { + pendingHost = daemonConfig.host + pendingPort = String(daemonConfig.port) + wcApiKey = UserDefaults.standard.string(forKey: "workingCopyAPIKey") ?? "" + } + .sheet(isPresented: $showAddProject) { + AddProjectView(projectStore: projectStore) + } } } + // MARK: - Actions + private func testConnection() async { connStatus = .testing - let host = pendingHost.trimmingCharacters(in:.whitespaces) + let host = pendingHost.trimmingCharacters(in: .whitespaces) let port = Int(pendingPort) ?? 8080 - guard let url = URL(string:"http://\(host):\(port)/health") else { connStatus = .failure("Invalid URL"); return } - var req = URLRequest(url:url); req.timeoutInterval = 4 + guard let url = URL(string: "http://\(host):\(port)/health") else { + connStatus = .failure("Invalid URL"); return + } + var req = URLRequest(url: url); req.timeoutInterval = 4 do { - let (_,resp) = try await URLSession.shared.data(for:req) - connStatus = (resp as? HTTPURLResponse)?.statusCode == 200 ? .success : .failure("Unexpected response") - } catch { connStatus = .failure(error.localizedDescription) } + let (_, resp) = try await URLSession.shared.data(for: req) + connStatus = (resp as? HTTPURLResponse)?.statusCode == 200 + ? .success + : .failure("Unexpected status code") + } catch { + connStatus = .failure(error.localizedDescription) + } } private func applyConnection() { - daemonConfig.host = pendingHost.trimmingCharacters(in:.whitespaces) + daemonConfig.host = pendingHost.trimmingCharacters(in: .whitespaces) daemonConfig.port = Int(pendingPort) ?? 8080 } } @@ -144,33 +258,57 @@ struct SettingsView: View { struct AddProjectView: View { @ObservedObject var projectStore: ProjectStore @Environment(\.dismiss) var dismiss - @State private var name = ""; @State private var path = "" - @State private var scheme = ""; @State private var wcRepo = "" + + @State private var name = "" + @State private var path = "" + @State private var scheme = "" + @State private var wcRepo = "" var body: some View { NavigationStack { Form { Section("Project Details") { - TextField("Name (e.g. NavidromePlayer)", text:$name) - TextField("/Users/dallas/Dev/NavidromePlayer/NavidromePlayer", text:$path).autocorrectionDisabled().autocapitalization(.none).keyboardType(.URL) - TextField("Xcode Scheme", text:$scheme).autocorrectionDisabled().autocapitalization(.none) + TextField("Name (e.g. NavidromePlayer)", text: $name) + TextField("/Users/dallas/Dev/NavidromePlayer/NavidromePlayer", text: $path) + .autocorrectionDisabled() + .autocapitalization(.none) + .keyboardType(.URL) + TextField("Xcode Scheme", text: $scheme) + .autocorrectionDisabled() + .autocapitalization(.none) + } + // Section with both header string AND footer requires the + // explicit header:/footer: trailing closure form. + Section { + TextField("Repo name in Working Copy (e.g. NavidromePlayer)", text: $wcRepo) + .autocorrectionDisabled() + .autocapitalization(.none) + } header: { + Text("Working Copy") + } footer: { + Text("Must match the display name in Working Copy's repo list.") } - Section("Working Copy") { - TextField("Repo name in Working Copy (e.g. NavidromePlayer)", text:$wcRepo).autocorrectionDisabled().autocapitalization(.none) - } footer: { Text("Must match the display name in Working Copy's repo list.") } } - .navigationTitle("Add Project").navigationBarTitleDisplayMode(.inline) + .navigationTitle("Add Project") + .navigationBarTitleDisplayMode(.inline) .toolbar { - ToolbarItem(placement:.cancellationAction) { Button("Cancel") { dismiss() } } - ToolbarItem(placement:.confirmationAction) { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } + } + ToolbarItem(placement: .confirmationAction) { Button("Add") { - projectStore.add(SavedProject(name: name.isEmpty ? scheme : name, - rootPath: path, scheme: scheme, - workingCopyRepoName: wcRepo)) + projectStore.add(SavedProject( + name: name.isEmpty ? scheme : name, + rootPath: path, + scheme: scheme, + workingCopyRepoName: wcRepo + )) dismiss() - }.disabled(path.isEmpty || scheme.isEmpty) + } + .disabled(path.isEmpty || scheme.isEmpty) } } - }.presentationDetents([.medium]) + } + .presentationDetents([.medium]) } } diff --git a/Shared/SharedModels.swift b/Shared/SharedModels.swift index b079c07..c9776e3 100644 --- a/Shared/SharedModels.swift +++ b/Shared/SharedModels.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import SwiftUI // MARK: - File System @@ -68,7 +69,7 @@ struct FileContentResponse: Codable { // MARK: - Console -struct ConsoleLine: Identifiable { +struct ConsoleLine: Identifiable, Equatable { let id = UUID() let text: String let type: LineType @@ -159,6 +160,9 @@ extension Notification.Name { static let navigateToRange = Notification.Name("editor.navigate.range") static let triggerCompletion = Notification.Name("editor.trigger.completion") static let openSettings = Notification.Name("padxcode.open.settings") + static let editorFindNext = Notification.Name("editor.find.next") + static let editorFindPrev = Notification.Name("editor.find.prev") + static let editorFontSizeChange = Notification.Name("editor.font.size.change") } // MARK: - String helpers @@ -172,3 +176,20 @@ extension String { return self.replacingCharacters(in: range, with: replacement) } } + +// MARK: - UIColor hex convenience (used by themes and editor views) + +extension UIColor { + convenience init(hex: String) { + var h = hex.trimmingCharacters(in: .whitespacesAndNewlines) + h = h.hasPrefix("#") ? String(h.dropFirst()) : h + var rgb: UInt64 = 0 + Scanner(string: h).scanHexInt64(&rgb) + self.init( + red: CGFloat((rgb >> 16) & 0xFF) / 255.0, + green: CGFloat((rgb >> 8) & 0xFF) / 255.0, + blue: CGFloat( rgb & 0xFF) / 255.0, + alpha: 1.0 + ) + } +} diff --git a/Theme/LanguageDetector.swift b/Theme/LanguageDetector.swift index e39d4ed..6b89f62 100644 --- a/Theme/LanguageDetector.swift +++ b/Theme/LanguageDetector.swift @@ -1,3 +1,4 @@ +import Foundation import Runestone import TreeSitterSwiftRunestone import TreeSitterJSONRunestone diff --git a/Theme/PadXcodeTheme.swift b/Theme/PadXcodeTheme.swift index fa826fa..dccf2f9 100644 --- a/Theme/PadXcodeTheme.swift +++ b/Theme/PadXcodeTheme.swift @@ -1,151 +1,262 @@ import UIKit import Runestone -final class PadXcodeTheme: Theme { - var backgroundColor: UIColor { UIColor(hex: "#1E1E1E") } - var userInterfaceStyle: UIUserInterfaceStyle { .dark } - var font: UIFont { UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) } - var textColor: UIColor { UIColor(hex: "#D4D4D4") } - var gutterBackgroundColor: UIColor { UIColor(hex: "#252526") } - var gutterHairlineColor: UIColor { UIColor(hex: "#3C3C3C") } - var lineNumberColor: UIColor { UIColor(hex: "#6E7681") } - var lineNumberFont: UIFont { UIFont.monospacedSystemFont(ofSize: 12, weight: .regular) } - var selectedLineBackgroundColor: UIColor { UIColor(hex: "#2A2D2E") } - var selectedLinesLineNumberColor: UIColor { UIColor(hex: "#C8C8C8") } - var selectedLinesGutterBackgroundColor: UIColor { UIColor(hex: "#2A2D2E") } - var markedTextBackgroundColor: UIColor { UIColor(hex: "#264F78").withAlphaComponent(0.4) } - var markedTextBackgroundCornerRadius: CGFloat { 2 } - var invisibleCharactersColor: UIColor { UIColor(hex: "#404040") } - var pageGuideHairlineColor: UIColor { UIColor(hex: "#404040") } - var pageGuideBackgroundColor: UIColor { UIColor(hex: "#1A1A1A") } +// MARK: - Xcode Default Dark Theme - func textStyle(for highlightName: String) -> TextStyle? { +final class PadXcodeTheme: Theme { + + // MARK: Required visual properties + + var font: UIFont { + UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) + } + + var textColor: UIColor { + UIColor(r: 212, g: 212, b: 212) + } + + var backgroundColor: UIColor { + UIColor(r: 30, g: 30, b: 30) + } + + var userInterfaceStyle: UIUserInterfaceStyle { + .dark + } + + var gutterBackgroundColor: UIColor { + UIColor(r: 37, g: 37, b: 38) + } + + var gutterHairlineColor: UIColor { + UIColor(r: 60, g: 60, b: 60) + } + + var lineNumberColor: UIColor { + UIColor(r: 110, g: 118, b: 129) + } + + var lineNumberFont: UIFont { + UIFont.monospacedSystemFont(ofSize: 12, weight: .regular) + } + + var selectedLineBackgroundColor: UIColor { + UIColor(r: 42, g: 45, b: 46) + } + + var selectedLinesLineNumberColor: UIColor { + UIColor(r: 200, g: 200, b: 200) + } + + var selectedLinesGutterBackgroundColor: UIColor { + UIColor(r: 42, g: 45, b: 46) + } + + var markedTextBackgroundColor: UIColor { + UIColor(r: 38, g: 79, b: 120, alpha: 0.4) + } + + var markedTextBackgroundCornerRadius: CGFloat { + 2 + } + + var invisibleCharactersColor: UIColor { + UIColor(r: 64, g: 64, b: 64) + } + + var pageGuideHairlineColor: UIColor { + UIColor(r: 64, g: 64, b: 64) + } + + var pageGuideBackgroundColor: UIColor { + UIColor(r: 26, g: 26, b: 26) + } + + // MARK: Syntax token colours (required by Theme protocol) + + func textColor(for highlightName: String) -> UIColor? { switch highlightName { - case "keyword", "keyword.control", "keyword.operator", "keyword.other", - "keyword.declaration", "storage.type": - return TextStyle(color: UIColor(hex: "#FF7AB2"), bold: true) - case "storage.modifier": - return TextStyle(color: UIColor(hex: "#FF7AB2")) - case "type", "type.builtin", "entity.name.type", "entity.name.type.class", - "entity.name.type.struct", "entity.name.type.enum", "entity.name.type.protocol": - return TextStyle(color: UIColor(hex: "#DABAFF")) + case "keyword", "keyword.control", "keyword.operator", + "keyword.other", "keyword.declaration", "storage.type", + "storage.modifier": + return UIColor(r: 255, g: 122, b: 178) // pink + + case "type", "type.builtin", "entity.name.type", + "entity.name.type.class", "entity.name.type.struct", + "entity.name.type.enum", "entity.name.type.protocol": + return UIColor(r: 218, g: 186, b: 255) // lavender + case "type.parameter": - return TextStyle(color: UIColor(hex: "#6BDFFF")) - case "function", "entity.name.function", "meta.function-call", "function.method": - return TextStyle(color: UIColor(hex: "#67B7FB")) - case "function.builtin": - return TextStyle(color: UIColor(hex: "#67B7FB"), bold: true) - case "variable", "variable.other", "variable.other.member": - return TextStyle(color: UIColor(hex: "#D4D4D4")) - case "variable.builtin": - return TextStyle(color: UIColor(hex: "#FF7AB2")) + return UIColor(r: 107, g: 223, b: 255) // light blue + + case "function", "entity.name.function", + "meta.function-call", "function.method", "function.builtin": + return UIColor(r: 103, g: 183, b: 251) // blue + case "property", "variable.other.property": - return TextStyle(color: UIColor(hex: "#72B9D5")) + return UIColor(r: 114, g: 185, b: 213) // teal + + case "variable.builtin": + return UIColor(r: 255, g: 122, b: 178) // pink (self, super) + case "constant", "constant.builtin": - return TextStyle(color: UIColor(hex: "#D9C97C"), bold: true) + return UIColor(r: 217, g: 201, b: 124) // gold bold + case "constant.numeric", "number": - return TextStyle(color: UIColor(hex: "#D9C97C")) + return UIColor(r: 217, g: 201, b: 124) // gold + case "string", "string.special": - return TextStyle(color: UIColor(hex: "#FF8170")) + return UIColor(r: 255, g: 129, b: 112) // salmon + case "string.escape": - return TextStyle(color: UIColor(hex: "#FF8170"), bold: true) - case "comment", "comment.line", "comment.block": - return TextStyle(color: UIColor(hex: "#7F8C98"), italic: true) - case "comment.block.documentation": - return TextStyle(color: UIColor(hex: "#6A9955"), italic: true) - case "operator", "punctuation.operator", "punctuation.bracket", "punctuation.delimiter": - return TextStyle(color: UIColor(hex: "#D4D4D4")) + return UIColor(r: 255, g: 129, b: 112) // salmon + + case "comment", "comment.line", "comment.block", + "comment.block.documentation": + return UIColor(r: 127, g: 140, b: 152) // grey + case "attribute": - return TextStyle(color: UIColor(hex: "#BF86C8")) + return UIColor(r: 191, g: 134, b: 200) // purple + + case "operator", "punctuation.operator", + "punctuation.bracket", "punctuation.delimiter": + return UIColor(r: 212, g: 212, b: 212) // default text + default: return nil } } } +// MARK: - Xcode Default Light Theme + final class PadXcodeLightTheme: Theme { - var backgroundColor: UIColor { UIColor(hex: "#FFFFFF") } - var userInterfaceStyle: UIUserInterfaceStyle { .light } - var font: UIFont { UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) } - var textColor: UIColor { UIColor(hex: "#000000") } - var gutterBackgroundColor: UIColor { UIColor(hex: "#F2F2F2") } - var gutterHairlineColor: UIColor { UIColor(hex: "#D8D8D8") } - var lineNumberColor: UIColor { UIColor(hex: "#A0A0A0") } - var lineNumberFont: UIFont { UIFont.monospacedSystemFont(ofSize: 12, weight: .regular) } - var selectedLineBackgroundColor: UIColor { UIColor(hex: "#ECF5FF") } - var selectedLinesLineNumberColor: UIColor { UIColor(hex: "#3A3A3A") } - var selectedLinesGutterBackgroundColor: UIColor { UIColor(hex: "#E8F0FA") } - var markedTextBackgroundColor: UIColor { UIColor(hex: "#B5D0F7").withAlphaComponent(0.4) } - var markedTextBackgroundCornerRadius: CGFloat { 2 } - var invisibleCharactersColor: UIColor { UIColor(hex: "#CCCCCC") } - var pageGuideHairlineColor: UIColor { UIColor(hex: "#DDDDDD") } - var pageGuideBackgroundColor: UIColor { UIColor(hex: "#F9F9F9") } - func textStyle(for highlightName: String) -> TextStyle? { + // MARK: Required visual properties + + var font: UIFont { + UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) + } + + var textColor: UIColor { + UIColor(r: 0, g: 0, b: 0) + } + + var backgroundColor: UIColor { + UIColor(r: 255, g: 255, b: 255) + } + + var userInterfaceStyle: UIUserInterfaceStyle { + .light + } + + var gutterBackgroundColor: UIColor { + UIColor(r: 242, g: 242, b: 242) + } + + var gutterHairlineColor: UIColor { + UIColor(r: 216, g: 216, b: 216) + } + + var lineNumberColor: UIColor { + UIColor(r: 160, g: 160, b: 160) + } + + var lineNumberFont: UIFont { + UIFont.monospacedSystemFont(ofSize: 12, weight: .regular) + } + + var selectedLineBackgroundColor: UIColor { + UIColor(r: 236, g: 245, b: 255) + } + + var selectedLinesLineNumberColor: UIColor { + UIColor(r: 58, g: 58, b: 58) + } + + var selectedLinesGutterBackgroundColor: UIColor { + UIColor(r: 232, g: 240, b: 250) + } + + var markedTextBackgroundColor: UIColor { + UIColor(r: 181, g: 208, b: 247, alpha: 0.4) + } + + var markedTextBackgroundCornerRadius: CGFloat { + 2 + } + + var invisibleCharactersColor: UIColor { + UIColor(r: 204, g: 204, b: 204) + } + + var pageGuideHairlineColor: UIColor { + UIColor(r: 221, g: 221, b: 221) + } + + var pageGuideBackgroundColor: UIColor { + UIColor(r: 249, g: 249, b: 249) + } + + // MARK: Syntax token colours (required by Theme protocol) + + func textColor(for highlightName: String) -> UIColor? { switch highlightName { - case "keyword", "keyword.control", "keyword.operator", "keyword.other", - "keyword.declaration", "storage.type": - return TextStyle(color: UIColor(hex: "#AD3DA4"), bold: true) - case "storage.modifier": - return TextStyle(color: UIColor(hex: "#AD3DA4")) - case "type", "type.builtin", "entity.name.type", "entity.name.type.class", - "entity.name.type.struct", "entity.name.type.enum", "entity.name.type.protocol": - return TextStyle(color: UIColor(hex: "#703DAA")) + case "keyword", "keyword.control", "keyword.operator", + "keyword.other", "keyword.declaration", "storage.type", + "storage.modifier": + return UIColor(r: 173, g: 61, b: 164) // purple + + case "type", "type.builtin", "entity.name.type", + "entity.name.type.class", "entity.name.type.struct", + "entity.name.type.enum", "entity.name.type.protocol": + return UIColor(r: 112, g: 61, b: 170) // dark purple + case "type.parameter": - return TextStyle(color: UIColor(hex: "#047CB0")) - case "function", "entity.name.function", "meta.function-call", "function.method": - return TextStyle(color: UIColor(hex: "#3900A0")) - case "function.builtin": - return TextStyle(color: UIColor(hex: "#3900A0"), bold: true) - case "variable", "variable.other", "variable.other.member": - return TextStyle(color: UIColor(hex: "#000000")) - case "variable.builtin": - return TextStyle(color: UIColor(hex: "#AD3DA4")) + return UIColor(r: 4, g: 124, b: 176) // blue + + case "function", "entity.name.function", + "meta.function-call", "function.method", "function.builtin": + return UIColor(r: 57, g: 0, b: 160) // indigo + case "property", "variable.other.property": - return TextStyle(color: UIColor(hex: "#047CB0")) + return UIColor(r: 4, g: 124, b: 176) // blue + + case "variable.builtin": + return UIColor(r: 173, g: 61, b: 164) // purple (self, super) + case "constant", "constant.builtin": - return TextStyle(color: UIColor(hex: "#272AD8"), bold: true) + return UIColor(r: 39, g: 42, b: 216) // blue bold + case "constant.numeric", "number": - return TextStyle(color: UIColor(hex: "#272AD8")) + return UIColor(r: 39, g: 42, b: 216) // blue + case "string", "string.special": - return TextStyle(color: UIColor(hex: "#C41A16")) + return UIColor(r: 196, g: 26, b: 22) // red + case "string.escape": - return TextStyle(color: UIColor(hex: "#C41A16"), bold: true) - case "comment", "comment.line", "comment.block": - return TextStyle(color: UIColor(hex: "#5D6C79"), italic: true) - case "comment.block.documentation": - return TextStyle(color: UIColor(hex: "#265B31"), italic: true) - case "operator", "punctuation.operator", "punctuation.bracket", "punctuation.delimiter": - return TextStyle(color: UIColor(hex: "#000000")) + return UIColor(r: 196, g: 26, b: 22) // red + + case "comment", "comment.line", "comment.block", + "comment.block.documentation": + return UIColor(r: 93, g: 108, b: 121) // grey + case "attribute": - return TextStyle(color: UIColor(hex: "#6C36A9")) + return UIColor(r: 108, g: 54, b: 169) // purple + + case "operator", "punctuation.operator", + "punctuation.bracket", "punctuation.delimiter": + return nil // inherits default textColor + default: return nil } } } -// MARK: - TextStyle helpers +// MARK: - UIColor RGB convenience (private to this file) -extension TextStyle { - init(color: UIColor, bold: Bool = false, italic: Bool = false) { - self.init() - self.color = color - if bold { self.fontTraits.insert(.traitBold) } - if italic { self.fontTraits.insert(.traitItalic) } - } -} - -// MARK: - UIColor hex - -extension UIColor { - convenience init(hex: String) { - var h = hex.trimmingCharacters(in: .whitespacesAndNewlines) - h = h.hasPrefix("#") ? String(h.dropFirst()) : h - var rgb: UInt64 = 0 - Scanner(string: h).scanHexInt64(&rgb) - self.init(red: CGFloat((rgb >> 16) & 0xFF) / 255, - green: CGFloat((rgb >> 8) & 0xFF) / 255, - blue: CGFloat( rgb & 0xFF) / 255, alpha: 1) +private extension UIColor { + convenience init(r: CGFloat, g: CGFloat, b: CGFloat, alpha: CGFloat = 1.0) { + self.init(red: r / 255.0, green: g / 255.0, blue: b / 255.0, alpha: alpha) } } diff --git a/UI/ToastSystem.swift b/UI/ToastSystem.swift index c2b251f..c0bb067 100644 --- a/UI/ToastSystem.swift +++ b/UI/ToastSystem.swift @@ -15,7 +15,7 @@ struct Toast: Identifiable { var color: Color { switch self { case .success: return .green; case .error: return .red - case .warning: return .orange; case .info: return .accentColor; case .git: return .purple + case .warning: return .orange; case .info: return Color.accentColor; case .git: return .purple } } } diff --git a/project.yml b/project.yml index 51d512b..2313ba9 100644 --- a/project.yml +++ b/project.yml @@ -72,6 +72,7 @@ targets: base: INFOPLIST_FILE: Info.plist CODE_SIGN_ENTITLEMENTS: PadXcode.entitlements + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION: YES LD_RUNPATH_SEARCH_PATHS: - "$(inherited)" - "@executable_path/Frameworks" From 3054861c32d25873c87456ae933261f491887127 Mon Sep 17 00:00:00 2001 From: Dallas Groot Date: Sun, 12 Apr 2026 09:47:47 -0700 Subject: [PATCH 3/3] i think this is okay to commit --- .../UserInterfaceState.xcuserstate | Bin 18929 -> 19808 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/PadXcode.xcodeproj/project.xcworkspace/xcuserdata/dallasgroot.xcuserdatad/UserInterfaceState.xcuserstate b/PadXcode.xcodeproj/project.xcworkspace/xcuserdata/dallasgroot.xcuserdatad/UserInterfaceState.xcuserstate index ab5fd689fe15d4beaf4af50fc0f28d87e8c1f614..b8aa14a8e5282b9d03aa3d1625a7fb2f7f1175ba 100644 GIT binary patch delta 7834 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 delta 7004 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