From 83980b99dec26de57085e7d84901127ea4d1ce78 Mon Sep 17 00:00:00 2001 From: Paul Ebose Date: Sun, 19 May 2024 01:36:11 +0100 Subject: [PATCH 1/6] fix: allow text files to be edited even when empty --- CodeEdit/Features/Editor/Views/EditorAreaFileView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift b/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift index 18dad183f..57d1c170c 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift @@ -26,7 +26,7 @@ struct EditorAreaFileView: View { @ViewBuilder var editorAreaFileView: some View { if let document = file.fileDocument { - if let utType = document.utType, utType.conforms(to: .text) { + if let fileURL = document.fileURL, (try? String(contentsOf: fileURL)) != nil { CodeFileView(codeFile: document, textViewCoordinators: textViewCoordinators) } else { NonTextFileView(fileDocument: document) From a4d97fc3f37b03d92e8dc87f2b8aa9a972097977 Mon Sep 17 00:00:00 2001 From: Paul Ebose Date: Sun, 19 May 2024 17:29:17 +0100 Subject: [PATCH 2/6] wip: remove the need for loading file contents into memory --- CodeEdit/Features/Documents/CodeFileDocument.swift | 6 +++--- CodeEdit/Features/Editor/Views/CodeFileView.swift | 12 +++++++++--- .../Features/Editor/Views/EditorAreaFileView.swift | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CodeEdit/Features/Documents/CodeFileDocument.swift b/CodeEdit/Features/Documents/CodeFileDocument.swift index b92d529c6..169e35eff 100644 --- a/CodeEdit/Features/Documents/CodeFileDocument.swift +++ b/CodeEdit/Features/Documents/CodeFileDocument.swift @@ -26,7 +26,7 @@ final class CodeFileDocument: NSDocument, ObservableObject { let cursorPositions: [CursorPosition] } - @Published var content = "" + @Published var content: String? /// Used to override detected languages. @Published var language: CodeLanguage? @@ -51,7 +51,7 @@ final class CodeFileDocument: NSDocument, ObservableObject { /// - Note: The UTType doesn't necessarily mean the file extension, it can be the MIME /// type or any other form of data representation. var utType: UTType? { - if !self.content.isEmpty { + if self.content != nil { return .text } guard let fileType, let type = UTType(fileType) else { @@ -117,7 +117,7 @@ final class CodeFileDocument: NSDocument, ObservableObject { } override func data(ofType _: String) throws -> Data { - guard let data = content.data(using: .utf8) else { throw CodeFileError.failedToEncode } + guard let data = content?.data(using: .utf8) else { throw CodeFileError.failedToEncode } return data } diff --git a/CodeEdit/Features/Editor/Views/CodeFileView.swift b/CodeEdit/Features/Editor/Views/CodeFileView.swift index adcffbde9..5c60d6aca 100644 --- a/CodeEdit/Features/Editor/Views/CodeFileView.swift +++ b/CodeEdit/Features/Editor/Views/CodeFileView.swift @@ -54,10 +54,16 @@ struct CodeFileView: View { private let undoManager = CEUndoManager() + private var sourceEditorText: Binding + init(codeFile: CodeFileDocument, textViewCoordinators: [TextViewCoordinator] = [], isEditable: Bool = true) { self.codeFile = codeFile self.textViewCoordinators = textViewCoordinators self.isEditable = isEditable + self.sourceEditorText = Binding( + get: { codeFile.content ?? "" }, + set: { codeFile.content = $0 } + ) if let openOptions = codeFile.openOptions { codeFile.openOptions = nil @@ -109,7 +115,7 @@ struct CodeFileView: View { var body: some View { CodeEditSourceEditor( - $codeFile.content, + sourceEditorText, language: getLanguage(), theme: selectedTheme.editor.editorTheme, font: font, @@ -160,8 +166,8 @@ struct CodeFileView: View { } return codeFile.language ?? CodeLanguage.detectLanguageFrom( url: url, - prefixBuffer: codeFile.content.getFirstLines(5), - suffixBuffer: codeFile.content.getLastLines(5) + prefixBuffer: codeFile.content?.getFirstLines(5), + suffixBuffer: codeFile.content?.getLastLines(5) ) } diff --git a/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift b/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift index 57d1c170c..18dad183f 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift @@ -26,7 +26,7 @@ struct EditorAreaFileView: View { @ViewBuilder var editorAreaFileView: some View { if let document = file.fileDocument { - if let fileURL = document.fileURL, (try? String(contentsOf: fileURL)) != nil { + if let utType = document.utType, utType.conforms(to: .text) { CodeFileView(codeFile: document, textViewCoordinators: textViewCoordinators) } else { NonTextFileView(fileDocument: document) From c75939fc9230ea6a79a48e82f677ea0c935901eb Mon Sep 17 00:00:00 2001 From: Paul Ebose Date: Thu, 13 Jun 2024 04:46:15 +0100 Subject: [PATCH 3/6] xcode updated packages - CodeEditTextView 0.7.3 -> 0.7.4 - CodeEditSourceEditor 0.7.2 -> 0.7.3 - CodeEditLanguages 0.1.18 -> 0.1.19 --- .../xcshareddata/swiftpm/Package.resolved | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7790485df..bc1e0a860 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/CodeEditApp/CodeEditLanguages.git", "state" : { - "revision" : "620b463c88894741e20d4711c9435b33547de5d2", - "version" : "0.1.18" + "revision" : "5b27f139269e1ea49ceae5e56dca44a3ccad50a1", + "version" : "0.1.19" } }, { @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/CodeEditApp/CodeEditSourceEditor", "state" : { - "revision" : "7360f00bf7ec8e93b4833357bd254bef7e5c943d", - "version" : "0.7.2" + "revision" : "cf85789d527d569e94edfd674c5ac8071b244dd9", + "version" : "0.7.3" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/CodeEditApp/CodeEditTextView.git", "state" : { - "revision" : "86b980464bcb67693e2053283c7a99bdc6f358bc", - "version" : "0.7.3" + "revision" : "80911be6bcdae5e35ef5ed351adf6dda9b57e555", + "version" : "0.7.4" } }, { From 357e698ad4a3a728a9d1b74c286fd91a9fe6867e Mon Sep 17 00:00:00 2001 From: Paul Ebose Date: Thu, 13 Jun 2024 05:14:27 +0100 Subject: [PATCH 4/6] Merge branch 'main' of https://github.com/CodeEditApp/CodeEdit into allow-edit-text-file-when-contents-in-empty --- .all-contributorsrc | 9 + CodeEdit.xcodeproj/project.pbxproj | 57 +++++- .../About/Views/BlurButtonStyle.swift | 2 +- .../CodeEditUI/Views/KeyValueTable.swift | 172 ++++++++++++++++++ .../CodeEditUI/Views/PaneTextField.swift | 2 +- .../CodeEditUI/Views/SegmentedControl.swift | 2 +- .../Features/Documents/CodeFileDocument.swift | 39 +++- .../Features/Documents/FileEncoding.swift | 38 ++++ .../Documents/Indexer/SearchIndexer+Add.swift | 6 +- .../SearchIndexer+InternalMethods.swift | 25 ++- .../SearchIndexer+ProgressiveSearch.swift | 2 +- .../Indexer/SearchIndexer+Terms.swift | 4 +- .../Documents/Indexer/SearchIndexer.swift | 2 +- .../Editor/Models/EditorInstance.swift | 1 - .../Tabs/Tab/EditorTabCloseButton.swift | 2 +- .../Features/Editor/Views/CodeFileView.swift | 30 +-- .../Git/Client/GitClient+CommitHistory.swift | 7 +- .../Git/Client/GitClient+Remote.swift | 17 ++ CodeEdit/Features/Git/Client/GitClient.swift | 6 + .../Settings/Models/SettingsData.swift | 10 +- .../Settings/Models/SettingsPage.swift | 1 + .../AccountsSettingsAccountLink.swift | 2 +- .../AccountsSettingsProviderRow.swift | 2 +- .../AccountsSettingsSigninView.swift | 2 +- .../Models/AccountsSettings.swift | 2 +- .../DeveloperSettingsView.swift | 35 ++++ .../Models/DeveloperSettings.swift | 39 ++++ .../ThemeSettingsColorPreview.swift | 2 +- CodeEdit/Features/Settings/SettingsView.swift | 53 +++++- .../Toolbar/UtilityAreaFilterTextField.swift | 5 +- .../Welcome/Views/WelcomeActionView.swift | 2 +- .../Features/Welcome/Views/WelcomeView.swift | 2 +- .../NSTextStorage/NSTextStorage+isEmpty.swift | 14 ++ .../Features/CodeFile/CodeFileTests.swift | 34 +++- README.md | 2 + 35 files changed, 554 insertions(+), 76 deletions(-) create mode 100644 CodeEdit/Features/CodeEditUI/Views/KeyValueTable.swift create mode 100644 CodeEdit/Features/Documents/FileEncoding.swift create mode 100644 CodeEdit/Features/Settings/Pages/DeveloperSettings/DeveloperSettingsView.swift create mode 100644 CodeEdit/Features/Settings/Pages/DeveloperSettings/Models/DeveloperSettings.swift create mode 100644 CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift diff --git a/.all-contributorsrc b/.all-contributorsrc index e2f12bc1a..eb2e0999a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -731,6 +731,15 @@ "contributions": [ "bug" ] + }, + { + "login": "simonwhitaker", + "name": "Simon Whitaker", + "avatar_url": "https://avatars.githubusercontent.com/u/116432?v=4", + "profile": "https://github.com/simonwhitaker", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 48c7c157d..9830229f9 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -64,6 +64,9 @@ 3000516A2BBD3A8200A98562 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300051692BBD3A8200A98562 /* ServiceType.swift */; }; 3000516C2BBD3A9500A98562 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */; }; 3026F50F2AC006C80061227E /* InspectorAreaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */; }; + 30AB4EBB2BF718A100ED4431 /* DeveloperSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */; }; + 30AB4EBD2BF71CA800ED4431 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */; }; + 30AB4EC22BF7253200ED4431 /* KeyValueTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */; }; 30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */; }; 3E0196732A3921AC002648D8 /* codeedit_shell_integration.zsh in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */; }; 3E01967A2A392B45002648D8 /* codeedit_shell_integration.bash in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */; }; @@ -319,6 +322,8 @@ 6C4104E3297C87A000F472BA /* BlurButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E2297C87A000F472BA /* BlurButtonStyle.swift */; }; 6C4104E6297C884F00F472BA /* AboutDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E5297C884F00F472BA /* AboutDetailView.swift */; }; 6C4104E9297C970F00F472BA /* AboutDefaultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */; }; + 6C48B5C52C0A2835001E9955 /* FileEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48B5C42C0A2835001E9955 /* FileEncoding.swift */; }; + 6C48B5C92C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48B5C72C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift */; }; 6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */; }; 6C48D8F42972DB1A00D6D205 /* Env+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */; }; 6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */; }; @@ -633,6 +638,9 @@ 300051692BBD3A8200A98562 /* ServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceType.swift; sourceTree = ""; }; 3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = ""; }; 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorAreaViewModel.swift; sourceTree = ""; }; + 30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettings.swift; sourceTree = ""; }; + 30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsView.swift; sourceTree = ""; }; + 30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueTable.swift; sourceTree = ""; }; 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSidebarViewModel.swift; sourceTree = ""; }; 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.zsh; sourceTree = ""; }; 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.bash; sourceTree = ""; }; @@ -884,6 +892,8 @@ 6C4104E2297C87A000F472BA /* BlurButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurButtonStyle.swift; sourceTree = ""; }; 6C4104E5297C884F00F472BA /* AboutDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDetailView.swift; sourceTree = ""; }; 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDefaultView.swift; sourceTree = ""; }; + 6C48B5C42C0A2835001E9955 /* FileEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileEncoding.swift; sourceTree = ""; }; + 6C48B5C72C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextStorage+isEmpty.swift"; sourceTree = ""; }; 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+IsFullscreen.swift"; sourceTree = ""; }; 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+Window.swift"; sourceTree = ""; }; 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = ""; }; @@ -1123,6 +1133,7 @@ 5831E3CE2933F3DE00D5A6D2 /* Controllers */, 611191F82B08CC8000D4459B /* Indexer */, 58798249292E78D80085B254 /* CodeFileDocument.swift */, + 6C48B5C42C0A2835001E9955 /* FileEncoding.swift */, 043C321527E3201F006AE443 /* WorkspaceDocument.swift */, 043BCF02281DA18A000AC47C /* WorkspaceDocument+SearchState.swift */, 61A53A802B4449F00093BF8A /* WorkspaceDocument+Index.swift */, @@ -1327,6 +1338,23 @@ path = ViewModels; sourceTree = ""; }; + 30AB4EB72BF7170B00ED4431 /* DeveloperSettings */ = { + isa = PBXGroup; + children = ( + 30AB4EB92BF7189300ED4431 /* Models */, + 30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */, + ); + path = DeveloperSettings; + sourceTree = ""; + }; + 30AB4EB92BF7189300ED4431 /* Models */ = { + isa = PBXGroup; + children = ( + 30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */, + ); + path = Models; + sourceTree = ""; + }; 3E0196712A392170002648D8 /* ShellIntegration */ = { isa = PBXGroup; children = ( @@ -1767,6 +1795,7 @@ 587B9D8629300ABD00AC7927 /* Views */ = { isa = PBXGroup; children = ( + 30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */, B62AEDA92A1FCBE5009A9F52 /* AreaTabBar.swift */, B65B10EB2B073913002852CF /* CEContentUnavailableView.swift */, B65B10FA2B08B054002852CF /* Divided.swift */, @@ -2171,6 +2200,7 @@ 588847672992AAB800996D95 /* Array */, 6CBD1BC42978DE3E006639D5 /* Text */, 5831E3D02934036D00D5A6D2 /* NSTableView */, + 6C48B5C82C0B5F7A001E9955 /* NSTextStorage */, 5831E3CA2933E86F00D5A6D2 /* View */, 5831E3C72933E7F700D5A6D2 /* Bundle */, 5831E3C62933E7E600D5A6D2 /* Color */, @@ -2412,6 +2442,15 @@ path = FindNavigatorResultList; sourceTree = ""; }; + 6C48B5C82C0B5F7A001E9955 /* NSTextStorage */ = { + isa = PBXGroup; + children = ( + 6C48B5C72C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift */, + ); + name = NSTextStorage; + path = CodeEdit/Utils/Extensions/NSTextStorage; + sourceTree = SOURCE_ROOT; + }; 6C48D8EF2972DAC300D6D205 /* Environment */ = { isa = PBXGroup; children = ( @@ -2563,16 +2602,17 @@ B61DA9DD29D929BF00BF4A43 /* Pages */ = { isa = PBXGroup; children = ( - B664C3AD2B965F4500816B4E /* NavigationSettings */, - B61DA9E129D929F900BF4A43 /* GeneralSettings */, B6E41C6E29DD15540088F9F4 /* AccountsSettings */, - 58F2EAAE292FB2B0004A9BDE /* ThemeSettings */, - B6EA1FF329DA37D3001BF195 /* TextEditingSettings */, - 5B698A082B262F8400DE9392 /* SearchSettings */, + 30AB4EB72BF7170B00ED4431 /* DeveloperSettings */, + B61DA9E129D929F900BF4A43 /* GeneralSettings */, B6CF632629E5417C0085880A /* Keybindings */, B6F0516E29D9E35300D72287 /* LocationsSettings */, + B664C3AD2B965F4500816B4E /* NavigationSettings */, + 5B698A082B262F8400DE9392 /* SearchSettings */, B6F0516D29D9E34200D72287 /* SourceControlSettings */, B6F0516C29D9E32700D72287 /* TerminalSettings */, + B6EA1FF329DA37D3001BF195 /* TextEditingSettings */, + 58F2EAAE292FB2B0004A9BDE /* ThemeSettings */, ); path = Pages; sourceTree = ""; @@ -3459,9 +3499,11 @@ 5882252D292C280D00E83CDE /* UtilityAreaSplitTerminalButton.swift in Sources */, 58798238292E30B90085B254 /* FeedbackWindowController.swift in Sources */, 587B9E6C29301D8F00AC7927 /* GitLabNamespace.swift in Sources */, + 30AB4EC22BF7253200ED4431 /* KeyValueTable.swift in Sources */, 6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */, 587B9E8729301D8F00AC7927 /* GitHubRepositories.swift in Sources */, 6CE6226B2A2A1C730013085C /* UtilityAreaTab.swift in Sources */, + 6C48B5C52C0A2835001E9955 /* FileEncoding.swift in Sources */, 587B9DA329300ABD00AC7927 /* SettingsTextEditor.swift in Sources */, B6F0517B29D9E46400D72287 /* SourceControlSettingsView.swift in Sources */, 6C147C4D29A32AA30089B630 /* EditorAreaView.swift in Sources */, @@ -3584,6 +3626,7 @@ 04BA7C0E2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift in Sources */, 615AA21A2B0CFD480013FCCC /* LazyStringLoader.swift in Sources */, 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */, + 30AB4EBD2BF71CA800ED4431 /* DeveloperSettingsView.swift in Sources */, 6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */, 6CFF967629BEBCD900182D6F /* FileCommands.swift in Sources */, B60718462B17DC15009CDAB4 /* RepoOutlineGroupItem.swift in Sources */, @@ -3606,6 +3649,7 @@ 5878DA872918642F00DD95A3 /* AcknowledgementsViewModel.swift in Sources */, B6E41C7929DE02800088F9F4 /* AccountSelectionView.swift in Sources */, 6CA1AE952B46950000378EAB /* EditorInstance.swift in Sources */, + 30AB4EBB2BF718A100ED4431 /* DeveloperSettings.swift in Sources */, B6C4F2A92B3CB00100B2B140 /* CommitDetailsHeaderView.swift in Sources */, B6EA1FFB29DB78F6001BF195 /* ThemeSettingsThemeDetails.swift in Sources */, 587B9E7029301D8F00AC7927 /* GitLabUser.swift in Sources */, @@ -3710,6 +3754,7 @@ 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */, 613899BC2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift in Sources */, 611192002B08CCD700D4459B /* SearchIndexer+Memory.swift in Sources */, + 6C48B5C92C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift in Sources */, 587B9E8129301D8F00AC7927 /* PublicKey.swift in Sources */, 611191FE2B08CCD200D4459B /* SearchIndexer+File.swift in Sources */, 77A01E302BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift in Sources */, @@ -4904,7 +4949,7 @@ repositoryURL = "https://github.com/CodeEditApp/CodeEditSourceEditor"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.7.2; + minimumVersion = 0.7.3; }; }; 6CDEFC9429E22C2700B7C684 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { diff --git a/CodeEdit/Features/About/Views/BlurButtonStyle.swift b/CodeEdit/Features/About/Views/BlurButtonStyle.swift index 1ec9cc5ca..768f84131 100644 --- a/CodeEdit/Features/About/Views/BlurButtonStyle.swift +++ b/CodeEdit/Features/About/Views/BlurButtonStyle.swift @@ -50,6 +50,6 @@ struct BlurButtonStyle: ButtonStyle { Color.black } } - .cornerRadius(6) + .clipShape(RoundedRectangle(cornerRadius: 6)) } } diff --git a/CodeEdit/Features/CodeEditUI/Views/KeyValueTable.swift b/CodeEdit/Features/CodeEditUI/Views/KeyValueTable.swift new file mode 100644 index 000000000..32847dfd6 --- /dev/null +++ b/CodeEdit/Features/CodeEditUI/Views/KeyValueTable.swift @@ -0,0 +1,172 @@ +// +// KeyValueTable.swift +// CodeEdit +// +// Created by Abe Malla on 5/16/24. +// + +import SwiftUI + +struct KeyValueItem: Identifiable, Equatable { + let id = UUID() + let key: String + let value: String +} + +private struct NewListTableItemView: View { + @Environment(\.dismiss) + var dismiss + + @State private var key = "" + @State private var value = "" + + let keyColumnName: String + let valueColumnName: String + let newItemInstruction: String + let headerView: AnyView? + var completion: (String, String) -> Void + + init( + _ keyColumnName: String, + _ valueColumnName: String, + _ newItemInstruction: String, + headerView: AnyView? = nil, + completion: @escaping (String, String) -> Void + ) { + self.keyColumnName = keyColumnName + self.valueColumnName = valueColumnName + self.newItemInstruction = newItemInstruction + self.headerView = headerView + self.completion = completion + } + + var body: some View { + VStack(spacing: 0) { + Form { + Section { + TextField(keyColumnName, text: $key) + .textFieldStyle(.plain) + TextField(valueColumnName, text: $value) + .textFieldStyle(.plain) + } header: { + headerView + } + } + .formStyle(.grouped) + .scrollDisabled(true) + .scrollContentBackground(.hidden) + .onSubmit { + if !key.isEmpty && !value.isEmpty { + completion(key, value) + } + } + + HStack { + Spacer() + Button("Cancel") { + dismiss() + } + Button("Add") { + if !key.isEmpty && !value.isEmpty { + completion(key, value) + } + } + .buttonStyle(.borderedProminent) + .disabled(key.isEmpty || value.isEmpty) + } + .padding(.horizontal, 20) +// .padding(.top, 2) + .padding(.bottom, 20) + } + .frame(maxWidth: 480) + } +} + +struct KeyValueTable: View { + @Binding var items: [String: String] + + let keyColumnName: String + let valueColumnName: String + let newItemInstruction: String + let header: () -> Header + + @State private var showingModal = false + @State private var selection: UUID? + @State private var tableItems: [KeyValueItem] = [] + + init( + items: Binding<[String: String]>, + keyColumnName: String, + valueColumnName: String, + newItemInstruction: String, + @ViewBuilder header: @escaping () -> Header = { EmptyView() } + ) { + self._items = items + self.keyColumnName = keyColumnName + self.valueColumnName = valueColumnName + self.newItemInstruction = newItemInstruction + self.header = header + } + + var body: some View { + VStack { + Table(tableItems, selection: $selection) { + TableColumn(keyColumnName) { item in + Text(item.key) + } + TableColumn(valueColumnName) { item in + Text(item.value) + } + } + .frame(height: 200) + .actionBar { + HStack(spacing: 2) { + Button { + showingModal = true + } label: { + Image(systemName: "plus") + } + + Divider() + .frame(minHeight: 15) + + Button { + removeItem() + } label: { + Image(systemName: "minus") + } + .disabled(selection == nil) + .opacity(selection == nil ? 0.5 : 1) + } + Spacer() + } + .sheet(isPresented: $showingModal) { + NewListTableItemView( + keyColumnName, + valueColumnName, + newItemInstruction, + headerView: AnyView(header()) + ) { key, value in + items[key] = value + updateTableItems() + showingModal = false + } + } + .clipShape(RoundedRectangle(cornerRadius: 6)) + .onAppear(perform: updateTableItems) + } + } + + private func updateTableItems() { + tableItems = items.map { KeyValueItem(key: $0.key, value: $0.value) } + } + + private func removeItem() { + guard let selectedId = selection else { return } + if let selectedItem = tableItems.first(where: { $0.id == selectedId }) { + items.removeValue(forKey: selectedItem.key) + updateTableItems() + } + selection = nil + } +} diff --git a/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift b/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift index 560a45152..9c0d8e0ff 100644 --- a/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift +++ b/CodeEdit/Features/CodeEditUI/Views/PaneTextField.swift @@ -119,7 +119,7 @@ struct PaneTextField: View .overlay( RoundedRectangle(cornerRadius: 6) .stroke(isFocused || !text.isEmpty || hasValue ? .tertiary : .quaternary, lineWidth: 1.25) - .cornerRadius(6) + .clipShape(RoundedRectangle(cornerRadius: 6)) .disabled(true) .edgesIgnoringSafeArea(.all) ) diff --git a/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift b/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift index 0b142e54e..e91ce0abd 100644 --- a/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift +++ b/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift @@ -75,7 +75,7 @@ struct SegmentedControlItem: View { .background( background ) - .cornerRadius(5) + .clipShape(RoundedRectangle(cornerRadius: 5)) .onTapGesture { action() } diff --git a/CodeEdit/Features/Documents/CodeFileDocument.swift b/CodeEdit/Features/Documents/CodeFileDocument.swift index 169e35eff..0ba1edfde 100644 --- a/CodeEdit/Features/Documents/CodeFileDocument.swift +++ b/CodeEdit/Features/Documents/CodeFileDocument.swift @@ -26,7 +26,21 @@ final class CodeFileDocument: NSDocument, ObservableObject { let cursorPositions: [CursorPosition] } - @Published var content: String? + /// The text content of the document, stored as a text storage + /// + /// This is intentionally not a `@Published` variable. If it were published, SwiftUI would do a string + /// compare each time the contents are updated, which could cause a hang on each keystroke if the file is large + /// enough. + /// + /// To receive notifications for content updates, subscribe to one of the publishers on ``contentCoordinator``. + var content: NSTextStorage? + + /// The string encoding of the original file. Used to save the file back to the encoding it was loaded from. + var sourceEncoding: FileEncoding? + + /// The coordinator to use to subscribe to edit events and cursor location events. + /// See ``CodeEditSourceEditor/CombineCoordinator``. + @Published var contentCoordinator: CombineCoordinator = CombineCoordinator() /// Used to override detected languages. @Published var language: CodeLanguage? @@ -51,7 +65,7 @@ final class CodeFileDocument: NSDocument, ObservableObject { /// - Note: The UTType doesn't necessarily mean the file extension, it can be the MIME /// type or any other form of data representation. var utType: UTType? { - if self.content != nil { + if content != nil && content?.isEmpty ?? true { return .text } guard let fileType, let type = UTType(fileType) else { @@ -117,15 +131,30 @@ final class CodeFileDocument: NSDocument, ObservableObject { } override func data(ofType _: String) throws -> Data { - guard let data = content?.data(using: .utf8) else { throw CodeFileError.failedToEncode } + guard let sourceEncoding, let data = (content?.string as NSString?)?.data(using: sourceEncoding.nsValue) else { + throw CodeFileError.failedToEncode + } return data } /// This function is used for decoding files. /// It should not throw error as unsupported files can still be opened by QLPreviewView. override func read(from data: Data, ofType _: String) throws { - guard let content = String(data: data, encoding: .utf8) else { return } - self.content = content + var nsString: NSString? + let rawEncoding = NSString.stringEncoding( + for: data, + encodingOptions: [ + .allowLossyKey: false, // Fail if using lossy encoding. + .suggestedEncodingsKey: FileEncoding.allCases.map { $0.nsValue }, + .useOnlySuggestedEncodingsKey: true + ], + convertedString: &nsString, + usedLossyConversion: nil + ) + if let validEncoding = FileEncoding(rawEncoding), let nsString { + self.sourceEncoding = validEncoding + self.content = NSTextStorage(string: nsString as String) + } } /// Triggered when change occurred diff --git a/CodeEdit/Features/Documents/FileEncoding.swift b/CodeEdit/Features/Documents/FileEncoding.swift new file mode 100644 index 000000000..94da35470 --- /dev/null +++ b/CodeEdit/Features/Documents/FileEncoding.swift @@ -0,0 +1,38 @@ +// +// FileEncoding.swift +// CodeEdit +// +// Created by Khan Winter on 5/31/24. +// + +import Foundation + +enum FileEncoding: CaseIterable { + case utf8 + case utf16BE + case utf16LE + + var nsValue: UInt { + switch self { + case .utf8: + return NSUTF8StringEncoding + case .utf16BE: + return NSUTF16BigEndianStringEncoding + case .utf16LE: + return NSUTF16LittleEndianStringEncoding + } + } + + init?(_ int: UInt) { + switch int { + case NSUTF8StringEncoding: + self = .utf8 + case NSUTF16BigEndianStringEncoding: + self = .utf16BE + case NSUTF16LittleEndianStringEncoding: + self = .utf16LE + default: + return nil + } + } +} diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Add.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+Add.swift index 93743b4c3..bfbb19114 100644 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Add.swift +++ b/CodeEdit/Features/Documents/Indexer/SearchIndexer+Add.swift @@ -22,7 +22,7 @@ extension SearchIndexer { } return modifyIndexQueue.sync { - SKIndexAddDocumentWithText(index, document.takeUnretainedValue(), text as CFString, canReplace) + SKIndexAddDocumentWithText(index, document.takeRetainedValue(), text as CFString, canReplace) } } @@ -66,7 +66,7 @@ extension SearchIndexer { let mime = mimeType ?? self.detectMimeType(fileURL) return modifyIndexQueue.sync { - SKIndexAddDocument(index, document.takeUnretainedValue(), mime as CFString?, canReplace) + SKIndexAddDocument(index, document.takeRetainedValue(), mime as CFString?, canReplace) } } @@ -107,7 +107,7 @@ extension SearchIndexer { /// - Returns: `true` if the document was successfully removed, `false` otherwise. /// **Note:** If the document didn't exist, this also returns `true`. public func removeDocument(url: URL) -> Bool { - let document = SKDocumentCreateWithURL(url as CFURL).takeUnretainedValue() + let document = SKDocumentCreateWithURL(url as CFURL).takeRetainedValue() return self.remove(document: document) } diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+InternalMethods.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+InternalMethods.swift index 76cb54b8c..685f70c21 100644 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+InternalMethods.swift +++ b/CodeEdit/Features/Documents/Indexer/SearchIndexer+InternalMethods.swift @@ -64,25 +64,24 @@ extension SearchIndexer { var isLeaf = true - let iterator = SKIndexDocumentIteratorCreate(index, inParentDocument).takeUnretainedValue() + let iterator = SKIndexDocumentIteratorCreate(index, inParentDocument).takeRetainedValue() while let skDocument = SKIndexDocumentIteratorCopyNext(iterator) { isLeaf = false - self.addLeafURLs(index: index, inParentDocument: skDocument.takeUnretainedValue(), docs: &docs) + self.addLeafURLs(index: index, inParentDocument: skDocument.takeRetainedValue(), docs: &docs) } - if isLeaf, inParentDocument != nil, kSKDocumentStateNotIndexed != SKIndexGetDocumentState(index, inParentDocument) { - if let temp = SKDocumentCopyURL(inParentDocument) { - let baseURL = temp.takeUnretainedValue() as URL - let documentID = SKIndexGetDocumentID(index, inParentDocument) - docs.append( - DocumentID( - url: baseURL, - document: inParentDocument!, - documentID: documentID - ) + let url = SKDocumentCopyURL(inParentDocument).takeRetainedValue() + + let documentID = SKIndexGetDocumentID(index, inParentDocument) + docs.append( + DocumentID( + url: url as URL, + document: inParentDocument!, + documentID: documentID ) - } + ) + } } diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+ProgressiveSearch.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+ProgressiveSearch.swift index ba7b6d5b7..db06a8d0b 100644 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+ProgressiveSearch.swift +++ b/CodeEdit/Features/Documents/Indexer/SearchIndexer+ProgressiveSearch.swift @@ -93,7 +93,7 @@ extension SearchIndexer { let partialResult: [SearchResult] = zip(urls[0.. SearchResult? in - guard let url = cfurl?.takeUnretainedValue() as URL? else { + guard let url = cfurl?.takeRetainedValue() as URL? else { return nil } diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Terms.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer+Terms.swift index dadd5ccb7..0ebe0fd66 100644 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer+Terms.swift +++ b/CodeEdit/Features/Documents/Indexer/SearchIndexer+Terms.swift @@ -67,11 +67,11 @@ extension SearchIndexer { var result = [TermCount]() - let document = SKDocumentCreateWithURL(url as CFURL).takeUnretainedValue() + let document = SKDocumentCreateWithURL(url as CFURL).takeRetainedValue() let documentID = SKIndexGetDocumentID(index, document) guard let termVals = SKIndexCopyTermIDArrayForDocumentID(index, documentID), - let terms = termVals.takeUnretainedValue() as? [CFIndex] else { + let terms = termVals.takeRetainedValue() as? [CFIndex] else { return [] } diff --git a/CodeEdit/Features/Documents/Indexer/SearchIndexer.swift b/CodeEdit/Features/Documents/Indexer/SearchIndexer.swift index a5852e3fd..5b2150c6f 100644 --- a/CodeEdit/Features/Documents/Indexer/SearchIndexer.swift +++ b/CodeEdit/Features/Documents/Indexer/SearchIndexer.swift @@ -67,7 +67,7 @@ public class SearchIndexer { private(set) lazy var stopWords: Set = { var stopWords: Set = [] if let index = self.index, - let properties = SKIndexGetAnalysisProperties(self.index).takeUnretainedValue() as? [String: Any], + let properties = SKIndexGetAnalysisProperties(self.index).takeRetainedValue() as? [String: Any], let newStopWords = properties[kSKStopWords as String] as? Set { stopWords = newStopWords } diff --git a/CodeEdit/Features/Editor/Models/EditorInstance.swift b/CodeEdit/Features/Editor/Models/EditorInstance.swift index 6cc6895bb..f8aeb8ebc 100644 --- a/CodeEdit/Features/Editor/Models/EditorInstance.swift +++ b/CodeEdit/Features/Editor/Models/EditorInstance.swift @@ -77,7 +77,6 @@ class EditorInstance: Hashable { /// - Returns: The number of lines contained by the given range. Or `0` if the text view could not be found, /// or lines could not be found for the given range. func linesInRange(_ range: NSRange) -> Int { - // TODO: textView should be public, workaround for now guard let controller = textViewController, let scrollView = controller.view as? NSScrollView, let textView = scrollView.documentView as? TextView, diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift index 75b734cd5..f7d39e769 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift @@ -65,7 +65,7 @@ struct EditorTabCloseButton: View { ) ) .foregroundColor(isPressingClose ? .primary : .secondary) - .cornerRadius(2) + .clipShape(RoundedRectangle(cornerRadius: 2)) .contentShape(Rectangle()) .gesture( DragGesture(minimumDistance: 0) diff --git a/CodeEdit/Features/Editor/Views/CodeFileView.swift b/CodeEdit/Features/Editor/Views/CodeFileView.swift index 5c60d6aca..85572ed91 100644 --- a/CodeEdit/Features/Editor/Views/CodeFileView.swift +++ b/CodeEdit/Features/Editor/Views/CodeFileView.swift @@ -48,22 +48,16 @@ struct CodeFileView: View { @StateObject private var themeModel: ThemeModel = .shared - private var cancellables = [AnyCancellable]() + private var cancellables = Set() private let isEditable: Bool private let undoManager = CEUndoManager() - private var sourceEditorText: Binding - init(codeFile: CodeFileDocument, textViewCoordinators: [TextViewCoordinator] = [], isEditable: Bool = true) { - self.codeFile = codeFile - self.textViewCoordinators = textViewCoordinators + self._codeFile = .init(wrappedValue: codeFile) + self.textViewCoordinators = textViewCoordinators + [codeFile.contentCoordinator] self.isEditable = isEditable - self.sourceEditorText = Binding( - get: { codeFile.content ?? "" }, - set: { codeFile.content = $0 } - ) if let openOptions = codeFile.openOptions { codeFile.openOptions = nil @@ -71,16 +65,12 @@ struct CodeFileView: View { } codeFile - .$content - .dropFirst() - .debounce( - for: 0.25, - scheduler: DispatchQueue.main - ) + .contentCoordinator + .textUpdatePublisher + .debounce(for: 0.25, scheduler: DispatchQueue.main) .sink { _ in codeFile.updateChangeCount(.changeDone) - codeFile.autosave(withImplicitCancellability: false) { _ in - } + codeFile.autosave(withImplicitCancellability: false) { _ in } } .store(in: &cancellables) @@ -115,7 +105,7 @@ struct CodeFileView: View { var body: some View { CodeEditSourceEditor( - sourceEditorText, + codeFile.content ?? NSTextStorage(), language: getLanguage(), theme: selectedTheme.editor.editorTheme, font: font, @@ -166,8 +156,8 @@ struct CodeFileView: View { } return codeFile.language ?? CodeLanguage.detectLanguageFrom( url: url, - prefixBuffer: codeFile.content?.getFirstLines(5), - suffixBuffer: codeFile.content?.getLastLines(5) + prefixBuffer: codeFile.content?.string.getFirstLines(5), + suffixBuffer: codeFile.content?.string.getLastLines(5) ) } diff --git a/CodeEdit/Features/Git/Client/GitClient+CommitHistory.swift b/CodeEdit/Features/Git/Client/GitClient+CommitHistory.swift index a9c5c60bd..f2522ba1b 100644 --- a/CodeEdit/Features/Git/Client/GitClient+CommitHistory.swift +++ b/CodeEdit/Features/Git/Client/GitClient+CommitHistory.swift @@ -31,14 +31,13 @@ extension GitClient { dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss Z" let output = try await run( - "log --pretty=%h¦%H¦%s¦%aN¦%ae¦%cn¦%ce¦%aD¦%b¦%D¦ \(maxCountString) \(branchNameString) \(fileLocalPath)" + "log -z --pretty=%h¦%H¦%s¦%aN¦%ae¦%cn¦%ce¦%aD¦%b¦%D¦ \(maxCountString) \(branchNameString) \(fileLocalPath)" .trimmingCharacters(in: .whitespacesAndNewlines) ) - let remote = try await run("ls-remote --get-url") - let remoteURL = URL(string: remote.trimmingCharacters(in: .whitespacesAndNewlines)) + let remoteURL = try await getRemoteURL() return output - .split(separator: "\n") + .split(separator: "\0") .map { line -> GitCommit in let parameters = String(line).components(separatedBy: "¦") let infoRef = parameters[safe: 9] diff --git a/CodeEdit/Features/Git/Client/GitClient+Remote.swift b/CodeEdit/Features/Git/Client/GitClient+Remote.swift index b3f1b67d6..04307430b 100644 --- a/CodeEdit/Features/Git/Client/GitClient+Remote.swift +++ b/CodeEdit/Features/Git/Client/GitClient+Remote.swift @@ -31,6 +31,23 @@ extension GitClient { func removeRemote(name: String) async throws { _ = try await run("remote rm \(name)") } + + /// Get the URL of the remote + /// > Note: If a git repository has multiple remotes, by default the `origin` remote + /// > will be used, unless there’s an upstream branch configured for the current branch. + /// > (Reference: https://git-scm.com/docs/git-ls-remote, https://git-scm.com/docs/git-fetch) + /// - Returns: A URL if a remote is configured, nil otherwise + /// - Throws: `GitClientError.outputError` if the underlying git command fails unexpectedly + func getRemoteURL() async throws -> URL? { + do { + let remote = try await run("ls-remote --get-url") + return URL(string: remote.trimmingCharacters(in: .whitespacesAndNewlines)) + } catch GitClientError.noRemoteConfigured { + return nil + } catch { + throw error + } + } } func parseGitRemotes(from output: String) -> [GitRemote] { diff --git a/CodeEdit/Features/Git/Client/GitClient.swift b/CodeEdit/Features/Git/Client/GitClient.swift index a8f68dec9..67162f665 100644 --- a/CodeEdit/Features/Git/Client/GitClient.swift +++ b/CodeEdit/Features/Git/Client/GitClient.swift @@ -13,12 +13,14 @@ class GitClient { case outputError(String) case notGitRepository case failedToDecodeURL + case noRemoteConfigured var description: String { switch self { case .outputError(let string): string case .notGitRepository: "Not a git repository" case .failedToDecodeURL: "Failed to decode URL" + case .noRemoteConfigured: "No remote configured" } } } @@ -63,6 +65,10 @@ class GitClient { throw GitClientError.notGitRepository } + if output.contains("fatal: No remote configured") { + throw GitClientError.noRemoteConfigured + } + if output.hasPrefix("fatal:") { throw GitClientError.outputError(output) } diff --git a/CodeEdit/Features/Settings/Models/SettingsData.swift b/CodeEdit/Features/Settings/Models/SettingsData.swift index 1e0463673..7ba65b699 100644 --- a/CodeEdit/Features/Settings/Models/SettingsData.swift +++ b/CodeEdit/Features/Settings/Models/SettingsData.swift @@ -47,9 +47,12 @@ struct SettingsData: Codable, Hashable { /// The global settings for keybindings var keybindings: KeybindingsSettings = .init() - /// Searh Settings + /// Search Settings var search: SearchSettings = .init() + /// Developer settings for CodeEdit developers + var developerSettings: DeveloperSettings = .init() + /// Default initializer init() {} @@ -71,6 +74,9 @@ struct SettingsData: Codable, Hashable { KeybindingsSettings.self, forKey: .keybindings ) ?? .init() + self.developerSettings = try container.decodeIfPresent( + DeveloperSettings.self, forKey: .developerSettings + ) ?? .init() } // swiftlint:disable cyclomatic_complexity @@ -96,6 +102,8 @@ struct SettingsData: Codable, Hashable { sourceControl.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } case .location: LocationsSettings().searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .developer: + developerSettings.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } case .behavior: return [.init(name, settingName: "Error")] case .components: return [.init(name, settingName: "Error")] case .keybindings: return [.init(name, settingName: "Error")] diff --git a/CodeEdit/Features/Settings/Models/SettingsPage.swift b/CodeEdit/Features/Settings/Models/SettingsPage.swift index d8caed993..ff45c21a0 100644 --- a/CodeEdit/Features/Settings/Models/SettingsPage.swift +++ b/CodeEdit/Features/Settings/Models/SettingsPage.swift @@ -32,6 +32,7 @@ struct SettingsPage: Hashable, Equatable, Identifiable { case components = "Components" case location = "Locations" case advanced = "Advanced" + case developer = "Developer" } let id: UUID = UUID() diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift index ed3621cd2..51a92e099 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift @@ -25,7 +25,7 @@ struct AccountsSettingsAccountLink: View { Image(account.provider.iconName) .resizable() .aspectRatio(contentMode: .fill) - .cornerRadius(6) + .clipShape(RoundedRectangle(cornerRadius: 6)) .frame(width: 26, height: 26) .padding(.top, 2) .padding(.bottom, 2) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift index 154661d78..d986fc7db 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift @@ -20,7 +20,7 @@ struct AccountsSettingsProviderRow: View { Image(iconName) .resizable() .aspectRatio(contentMode: .fill) - .cornerRadius(6) + .clipShape(RoundedRectangle(cornerRadius: 6)) .frame(width: 28, height: 28) Text(name) Spacer() diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift index b2965e16a..3d20848f3 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift @@ -67,7 +67,7 @@ struct AccountsSettingsSigninView: View { Image(provider.iconName) .resizable() .aspectRatio(contentMode: .fill) - .cornerRadius(12) + .clipShape(RoundedRectangle(cornerRadius: 12)) .frame(width: 52, height: 52) .padding(.top, 5) Text("Sign in to \(provider.name)") diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift index b0220a2fc..d565d9d84 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/AccountsSettings.swift @@ -11,7 +11,7 @@ extension SettingsData { /// The global settings for text editing struct AccountsSettings: Codable, Hashable, SearchableSettingsPage { - /// An integer indicating how many spaces a `tab` will generate + /// The list of git accounts the user has saved var sourceControlAccounts: GitAccounts = .init() /// The search keys diff --git a/CodeEdit/Features/Settings/Pages/DeveloperSettings/DeveloperSettingsView.swift b/CodeEdit/Features/Settings/Pages/DeveloperSettings/DeveloperSettingsView.swift new file mode 100644 index 000000000..315c5e1c5 --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/DeveloperSettings/DeveloperSettingsView.swift @@ -0,0 +1,35 @@ +// +// DeveloperSettingsView.swift +// CodeEdit +// +// Created by Abe Malla on 5/16/24. +// + +import SwiftUI + +/// A view that implements the Developer settings section +struct DeveloperSettingsView: View { + @AppSettings(\.developerSettings.lspBinaries) + var lspBinaries + + var body: some View { + SettingsForm { + Section { + KeyValueTable( + items: $lspBinaries, + keyColumnName: "Language", + valueColumnName: "Language Server Path", + newItemInstruction: "Add a language server" + ) { + Text("Add a language server") + Text( + "Specify the absolute path to your LSP binary and its associated language." + ) + } + } header: { + Text("LSP Binaries") + Text("Specify the language and the absolute path to the language server binary.") + } + } + } +} diff --git a/CodeEdit/Features/Settings/Pages/DeveloperSettings/Models/DeveloperSettings.swift b/CodeEdit/Features/Settings/Pages/DeveloperSettings/Models/DeveloperSettings.swift new file mode 100644 index 000000000..cd142f36b --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/DeveloperSettings/Models/DeveloperSettings.swift @@ -0,0 +1,39 @@ +// +// DeveloperSettings.swift +// CodeEdit +// +// Created by Abe Malla on 5/15/24. +// + +import Foundation + +extension SettingsData { + struct DeveloperSettings: Codable, Hashable, SearchableSettingsPage { + + /// The search keys + var searchKeys: [String] { + [ + "Developer", + "Language Server Protocol", + "LSP Binaries" + ] + .map { NSLocalizedString($0, comment: "") } + } + + /// A dictionary that stores a file type and a path to an LSP binary + var lspBinaries: [String: String] = [:] + + /// Default initializer + init() {} + + /// Explicit decoder init for setting default values when key is not present in `JSON` + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.lspBinaries = try container.decodeIfPresent( + [String: String].self, + forKey: .lspBinaries + ) ?? [:] + } + } +} diff --git a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsColorPreview.swift b/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsColorPreview.swift index 77f39f9f5..42e2f9752 100644 --- a/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsColorPreview.swift +++ b/CodeEdit/Features/Settings/Pages/ThemeSettings/ThemeSettingsColorPreview.swift @@ -78,7 +78,7 @@ struct ThemeSettingsColorPreviewColor: View { var body: some View { color .frame(width: 5, height: 5) - .cornerRadius(5) + .clipShape(RoundedRectangle(cornerRadius: 5)) .overlay { ZStack { Circle() diff --git a/CodeEdit/Features/Settings/SettingsView.swift b/CodeEdit/Features/Settings/SettingsView.swift index 8bc583efd..7eaa55eaf 100644 --- a/CodeEdit/Features/Settings/SettingsView.swift +++ b/CodeEdit/Features/Settings/SettingsView.swift @@ -17,6 +17,7 @@ struct SettingsView: View { /// Variables for the selected Page, the current search text and software updater @State private var selectedPage: SettingsPage = Self.pages[0].page @State private var searchText: String = "" + @State private var showDeveloperSettings: Bool = false @Environment(\.presentationMode) var presentationMode @@ -84,7 +85,14 @@ struct SettingsView: View { baseColor: .green, icon: .system("externaldrive.fill") ) - ) + ), + .init( + SettingsPage( + .developer, + baseColor: .pink, + icon: .system("bolt") + ) + ), ] @ObservedObject private var settings: Settings = .shared @@ -130,7 +138,11 @@ struct SettingsView: View { SettingsPageView(page, searchText: searchText) } } else if !page.isSetting { - SettingsPageView(page, searchText: searchText) + if page.name == .developer && !showDeveloperSettings { + EmptyView() + } else { + SettingsPageView(page, searchText: searchText) + } } } @@ -166,6 +178,8 @@ struct SettingsView: View { SourceControlSettingsView() case .location: LocationsSettingsView() + case .developer: + DeveloperSettingsView() default: Text("Implementation Needed").frame(alignment: .center) } @@ -191,6 +205,23 @@ struct SettingsView: View { .environmentObject(model) .onAppear { selectedPage = Self.pages[0].page + + // Monitor for the F12 key down event to toggle the developer settings + model.setKeyDownMonitor { event in + if event.keyCode == 111 { + showDeveloperSettings.toggle() + + // If the developer menu is hidden and is selected, go back to default page + if !showDeveloperSettings && selectedPage.name == .developer { + selectedPage = Self.pages[0].page + } + return nil + } + return event + } + } + .onDisappear { + model.removeKeyDownMonitor() } } } @@ -198,4 +229,22 @@ struct SettingsView: View { class SettingsViewModel: ObservableObject { @Published var backButtonVisible: Bool = false @Published var scrolledToTop: Bool = false + + /// Holds a monitor closure for the `keyDown` event + private var keyDownEventMonitor: Any? + + func setKeyDownMonitor(monitor: @escaping (NSEvent) -> NSEvent?) { + keyDownEventMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: monitor) + } + + func removeKeyDownMonitor() { + if let eventMonitor = keyDownEventMonitor { + NSEvent.removeMonitor(eventMonitor) + self.keyDownEventMonitor = nil + } + } + + deinit { + removeKeyDownMonitor() + } } diff --git a/CodeEdit/Features/UtilityArea/Toolbar/UtilityAreaFilterTextField.swift b/CodeEdit/Features/UtilityArea/Toolbar/UtilityAreaFilterTextField.swift index 523055b7b..5909ff0c8 100644 --- a/CodeEdit/Features/UtilityArea/Toolbar/UtilityAreaFilterTextField.swift +++ b/CodeEdit/Features/UtilityArea/Toolbar/UtilityAreaFilterTextField.swift @@ -27,10 +27,11 @@ struct UtilityAreaFilterTextField: View { .padding(.horizontal, 5) .frame(height: 22) .background(Color(nsColor: isFocused ? .textBackgroundColor : .quaternaryLabelColor)) - .cornerRadius(7) + .clipShape(RoundedRectangle(cornerRadius: 7)) .overlay( RoundedRectangle(cornerRadius: 7) - .stroke(isFocused ? .secondary : .tertiary, lineWidth: 0.75).cornerRadius(7) + .stroke(isFocused ? .secondary : .tertiary, lineWidth: 0.75) + .clipShape(RoundedRectangle(cornerRadius: 7)) ) } diff --git a/CodeEdit/Features/Welcome/Views/WelcomeActionView.swift b/CodeEdit/Features/Welcome/Views/WelcomeActionView.swift index 900feb6b3..ded382068 100644 --- a/CodeEdit/Features/Welcome/Views/WelcomeActionView.swift +++ b/CodeEdit/Features/Welcome/Views/WelcomeActionView.swift @@ -42,6 +42,6 @@ struct WelcomeActionButtonStyle: ButtonStyle { .padding(7) .frame(height: 36) .background(Color(.labelColor).opacity(configuration.isPressed ? 0.1 : 0.05)) - .cornerRadius(8) + .clipShape(RoundedRectangle(cornerRadius: 8)) } } diff --git a/CodeEdit/Features/Welcome/Views/WelcomeView.swift b/CodeEdit/Features/Welcome/Views/WelcomeView.swift index ca5357284..8e1d7e60d 100644 --- a/CodeEdit/Features/Welcome/Views/WelcomeView.swift +++ b/CodeEdit/Features/Welcome/Views/WelcomeView.swift @@ -141,7 +141,7 @@ struct WelcomeView: View { Rectangle() .frame(width: 104, height: 104) .foregroundColor(.accentColor) - .cornerRadius(24) + .clipShape(RoundedRectangle(cornerRadius: 24)) .blur(radius: 64) .opacity(0.5) } diff --git a/CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift b/CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift new file mode 100644 index 000000000..f86705db6 --- /dev/null +++ b/CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift @@ -0,0 +1,14 @@ +// +// NSTextStorage+isEmpty.swift +// CodeEdit +// +// Created by Khan Winter on 5/19/24. +// + +import AppKit + +extension NSTextStorage { + var isEmpty: Bool { + length == 0 + } +} diff --git a/CodeEditTests/Features/CodeFile/CodeFileTests.swift b/CodeEditTests/Features/CodeFile/CodeFileTests.swift index 0cadeb3cc..8f5649136 100644 --- a/CodeEditTests/Features/CodeFile/CodeFileTests.swift +++ b/CodeEditTests/Features/CodeFile/CodeFileTests.swift @@ -11,7 +11,9 @@ import XCTest @testable import CodeEdit final class CodeFileUnitTests: XCTestCase { - func testViewContentLoading() throws { + var fileURL: URL! + + override func setUp() async throws { let directory = try FileManager.default.url( for: .developerApplicationDirectory, in: .userDomainMask, @@ -21,9 +23,10 @@ final class CodeFileUnitTests: XCTestCase { .appendingPathComponent("CodeEdit", isDirectory: true) .appendingPathComponent("WorkspaceClientTests", isDirectory: true) try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + fileURL = directory.appendingPathComponent("fakeFile.swift") + } - let fileURL = directory.appendingPathComponent("fakeFile.swift") - + func testLoadUTF8Encoding() throws { let fileContent = "func test(){}" try fileContent.data(using: .utf8)?.write(to: fileURL) @@ -32,6 +35,29 @@ final class CodeFileUnitTests: XCTestCase { withContentsOf: fileURL, ofType: "public.source-code" ) - XCTAssertEqual(codeFile.content, fileContent) + XCTAssertEqual(codeFile.content?.string, fileContent) + XCTAssertEqual(codeFile.sourceEncoding, .utf8) + } + + func testWriteUTF8Encoding() throws { + let codeFile = CodeFileDocument() + codeFile.content = NSTextStorage(string: "func test(){}") + codeFile.sourceEncoding = .utf8 + try codeFile.write(to: fileURL, ofType: "public.source-code") + + let data = try Data(contentsOf: fileURL) + var nsString: NSString? + let fileEncoding = NSString.stringEncoding( + for: data, + encodingOptions: [ + .suggestedEncodingsKey: FileEncoding.allCases.map { $0.nsValue }, + .useOnlySuggestedEncodingsKey: true + ], + convertedString: &nsString, + usedLossyConversion: nil + ) + + XCTAssertEqual(codeFile.content?.string as NSString?, nsString) + XCTAssertEqual(fileEncoding, NSUTF8StringEncoding) } } diff --git a/README.md b/README.md index bb239574e..f70c5f8f1 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ For issues we want to focus on that are most relevant at any given time, please Daniel Zhu
Daniel Zhu

🐛 + Simon Whitaker
Simon Whitaker

🐛 @@ -205,6 +206,7 @@ Support CodeEdit's development by [becoming a sponsor](https://github.com/sponso + ## Backers From 45c39ce9e17860fa8d8394cd320663a998e536f1 Mon Sep 17 00:00:00 2001 From: Paul Ebose Date: Thu, 13 Jun 2024 05:58:51 +0100 Subject: [PATCH 5/6] remove extra checks in `CodeFileDocument.utType` Only check for when `CodeFileDocument.content` is not nil. If the content is an empty string, it means we're viewing an empty text file, which is a predictable behavior. Also, the extra checks cause unexpected behaviors. Check the related pull request (#1724) for visuals. --- CodeEdit.xcodeproj/project.pbxproj | 13 ------------- CodeEdit/Features/Documents/CodeFileDocument.swift | 2 +- .../NSTextStorage/NSTextStorage+isEmpty.swift | 14 -------------- 3 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 9830229f9..af364cc16 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -323,7 +323,6 @@ 6C4104E6297C884F00F472BA /* AboutDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E5297C884F00F472BA /* AboutDetailView.swift */; }; 6C4104E9297C970F00F472BA /* AboutDefaultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */; }; 6C48B5C52C0A2835001E9955 /* FileEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48B5C42C0A2835001E9955 /* FileEncoding.swift */; }; - 6C48B5C92C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48B5C72C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift */; }; 6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */; }; 6C48D8F42972DB1A00D6D205 /* Env+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */; }; 6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */; }; @@ -893,7 +892,6 @@ 6C4104E5297C884F00F472BA /* AboutDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDetailView.swift; sourceTree = ""; }; 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDefaultView.swift; sourceTree = ""; }; 6C48B5C42C0A2835001E9955 /* FileEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileEncoding.swift; sourceTree = ""; }; - 6C48B5C72C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextStorage+isEmpty.swift"; sourceTree = ""; }; 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+IsFullscreen.swift"; sourceTree = ""; }; 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+Window.swift"; sourceTree = ""; }; 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = ""; }; @@ -2200,7 +2198,6 @@ 588847672992AAB800996D95 /* Array */, 6CBD1BC42978DE3E006639D5 /* Text */, 5831E3D02934036D00D5A6D2 /* NSTableView */, - 6C48B5C82C0B5F7A001E9955 /* NSTextStorage */, 5831E3CA2933E86F00D5A6D2 /* View */, 5831E3C72933E7F700D5A6D2 /* Bundle */, 5831E3C62933E7E600D5A6D2 /* Color */, @@ -2442,15 +2439,6 @@ path = FindNavigatorResultList; sourceTree = ""; }; - 6C48B5C82C0B5F7A001E9955 /* NSTextStorage */ = { - isa = PBXGroup; - children = ( - 6C48B5C72C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift */, - ); - name = NSTextStorage; - path = CodeEdit/Utils/Extensions/NSTextStorage; - sourceTree = SOURCE_ROOT; - }; 6C48D8EF2972DAC300D6D205 /* Environment */ = { isa = PBXGroup; children = ( @@ -3754,7 +3742,6 @@ 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */, 613899BC2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift in Sources */, 611192002B08CCD700D4459B /* SearchIndexer+Memory.swift in Sources */, - 6C48B5C92C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift in Sources */, 587B9E8129301D8F00AC7927 /* PublicKey.swift in Sources */, 611191FE2B08CCD200D4459B /* SearchIndexer+File.swift in Sources */, 77A01E302BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift in Sources */, diff --git a/CodeEdit/Features/Documents/CodeFileDocument.swift b/CodeEdit/Features/Documents/CodeFileDocument.swift index 0ba1edfde..4201bf430 100644 --- a/CodeEdit/Features/Documents/CodeFileDocument.swift +++ b/CodeEdit/Features/Documents/CodeFileDocument.swift @@ -65,7 +65,7 @@ final class CodeFileDocument: NSDocument, ObservableObject { /// - Note: The UTType doesn't necessarily mean the file extension, it can be the MIME /// type or any other form of data representation. var utType: UTType? { - if content != nil && content?.isEmpty ?? true { + if content != nil { return .text } guard let fileType, let type = UTType(fileType) else { diff --git a/CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift b/CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift deleted file mode 100644 index f86705db6..000000000 --- a/CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// NSTextStorage+isEmpty.swift -// CodeEdit -// -// Created by Khan Winter on 5/19/24. -// - -import AppKit - -extension NSTextStorage { - var isEmpty: Bool { - length == 0 - } -} From 4bc864fffd59b11336f6379ea4898e2393cf8592 Mon Sep 17 00:00:00 2001 From: Paul Ebose Date: Thu, 13 Jun 2024 06:18:02 +0100 Subject: [PATCH 6/6] redo changes made in 45c39ce9e17860fa8d8394cd320663a998e536f1 --- CodeEdit.xcodeproj/project.pbxproj | 13 ------------- CodeEdit/Features/Documents/CodeFileDocument.swift | 2 +- .../NSTextStorage/NSTextStorage+isEmpty.swift | 14 -------------- 3 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 9830229f9..af364cc16 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -323,7 +323,6 @@ 6C4104E6297C884F00F472BA /* AboutDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E5297C884F00F472BA /* AboutDetailView.swift */; }; 6C4104E9297C970F00F472BA /* AboutDefaultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */; }; 6C48B5C52C0A2835001E9955 /* FileEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48B5C42C0A2835001E9955 /* FileEncoding.swift */; }; - 6C48B5C92C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48B5C72C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift */; }; 6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */; }; 6C48D8F42972DB1A00D6D205 /* Env+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */; }; 6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */; }; @@ -893,7 +892,6 @@ 6C4104E5297C884F00F472BA /* AboutDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDetailView.swift; sourceTree = ""; }; 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDefaultView.swift; sourceTree = ""; }; 6C48B5C42C0A2835001E9955 /* FileEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileEncoding.swift; sourceTree = ""; }; - 6C48B5C72C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextStorage+isEmpty.swift"; sourceTree = ""; }; 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+IsFullscreen.swift"; sourceTree = ""; }; 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+Window.swift"; sourceTree = ""; }; 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = ""; }; @@ -2200,7 +2198,6 @@ 588847672992AAB800996D95 /* Array */, 6CBD1BC42978DE3E006639D5 /* Text */, 5831E3D02934036D00D5A6D2 /* NSTableView */, - 6C48B5C82C0B5F7A001E9955 /* NSTextStorage */, 5831E3CA2933E86F00D5A6D2 /* View */, 5831E3C72933E7F700D5A6D2 /* Bundle */, 5831E3C62933E7E600D5A6D2 /* Color */, @@ -2442,15 +2439,6 @@ path = FindNavigatorResultList; sourceTree = ""; }; - 6C48B5C82C0B5F7A001E9955 /* NSTextStorage */ = { - isa = PBXGroup; - children = ( - 6C48B5C72C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift */, - ); - name = NSTextStorage; - path = CodeEdit/Utils/Extensions/NSTextStorage; - sourceTree = SOURCE_ROOT; - }; 6C48D8EF2972DAC300D6D205 /* Environment */ = { isa = PBXGroup; children = ( @@ -3754,7 +3742,6 @@ 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */, 613899BC2B6E709C00A5CAF6 /* URL+FuzzySearchable.swift in Sources */, 611192002B08CCD700D4459B /* SearchIndexer+Memory.swift in Sources */, - 6C48B5C92C0B5F7A001E9955 /* NSTextStorage+isEmpty.swift in Sources */, 587B9E8129301D8F00AC7927 /* PublicKey.swift in Sources */, 611191FE2B08CCD200D4459B /* SearchIndexer+File.swift in Sources */, 77A01E302BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift in Sources */, diff --git a/CodeEdit/Features/Documents/CodeFileDocument.swift b/CodeEdit/Features/Documents/CodeFileDocument.swift index 0ba1edfde..4201bf430 100644 --- a/CodeEdit/Features/Documents/CodeFileDocument.swift +++ b/CodeEdit/Features/Documents/CodeFileDocument.swift @@ -65,7 +65,7 @@ final class CodeFileDocument: NSDocument, ObservableObject { /// - Note: The UTType doesn't necessarily mean the file extension, it can be the MIME /// type or any other form of data representation. var utType: UTType? { - if content != nil && content?.isEmpty ?? true { + if content != nil { return .text } guard let fileType, let type = UTType(fileType) else { diff --git a/CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift b/CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift deleted file mode 100644 index f86705db6..000000000 --- a/CodeEdit/Utils/Extensions/NSTextStorage/NSTextStorage+isEmpty.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// NSTextStorage+isEmpty.swift -// CodeEdit -// -// Created by Khan Winter on 5/19/24. -// - -import AppKit - -extension NSTextStorage { - var isEmpty: Bool { - length == 0 - } -}