diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index c55c44790..7243a0881 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -310,7 +310,6 @@ 613DF55E2B08DD5D00E9D902 /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613DF55D2B08DD5D00E9D902 /* FileHelper.swift */; }; 61538B902B111FE800A88846 /* String+AppearancesOfSubstring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61538B8F2B111FE800A88846 /* String+AppearancesOfSubstring.swift */; }; 61538B932B11201900A88846 /* String+Character.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61538B922B11201900A88846 /* String+Character.swift */; }; - 615AA21A2B0CFD480013FCCC /* LazyStringLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615AA2192B0CFD480013FCCC /* LazyStringLoader.swift */; }; 617DB3D02C25AFAE00B58BFE /* TaskNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3CF2C25AFAE00B58BFE /* TaskNotificationHandler.swift */; }; 617DB3D32C25AFEA00B58BFE /* TaskNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3D22C25AFEA00B58BFE /* TaskNotificationModel.swift */; }; 617DB3D62C25B02D00B58BFE /* TaskNotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3D52C25B02D00B58BFE /* TaskNotificationView.swift */; }; @@ -384,8 +383,6 @@ 6C5FDF7A29E6160000BC08C0 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5FDF7929E6160000BC08C0 /* AppSettings.swift */; }; 6C6362D42C3E321A0025570D /* Editor+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6362D32C3E321A0025570D /* Editor+History.swift */; }; 6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 6C66C31229D05CDC00DE9ED2 /* GRDB */; }; - 6C67413E2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */; }; - 6C6741402C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */; }; 6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */; }; 6C6BD6F129CD13FA00235D17 /* ExtensionDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */; }; 6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */; }; @@ -424,7 +421,11 @@ 6CBA0D512A1BF524002C6FAA /* SegmentedControlImproved.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBA0D502A1BF524002C6FAA /* SegmentedControlImproved.swift */; }; 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */; }; 6CBE1CFB2B71DAA6003AC32E /* Loopable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBE1CFA2B71DAA6003AC32E /* Loopable.swift */; }; - 6CBE1D002B720565003AC32E /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CBE1CFF2B720565003AC32E /* CodeEditSourceEditor */; }; + 6CC17B4F2C432AE000834E2C /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CC17B4E2C432AE000834E2C /* CodeEditSourceEditor */; }; + 6CC17B512C43311900834E2C /* ProjectNavigatorViewController+NSOutlineViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC17B502C43311900834E2C /* ProjectNavigatorViewController+NSOutlineViewDataSource.swift */; }; + 6CC17B532C43314000834E2C /* ProjectNavigatorViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC17B522C43314000834E2C /* ProjectNavigatorViewController+NSOutlineViewDelegate.swift */; }; + 6CC17B592C43F53700834E2C /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6CC17B582C43F53700834E2C /* CodeEditSourceEditor */; }; + 6CC17B5B2C44258700834E2C /* WindowControllerPropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC17B5A2C44258700834E2C /* WindowControllerPropertyWrapper.swift */; }; 6CC9E4B229B5669900C97388 /* Environment+ActiveEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC9E4B129B5669900C97388 /* Environment+ActiveEditor.swift */; }; 6CD03B6A29FC773F001BD1D0 /* SettingsInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD03B6929FC773F001BD1D0 /* SettingsInjector.swift */; }; 6CDA84AD284C1BA000C1CC3A /* EditorTabBarContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDA84AC284C1BA000C1CC3A /* EditorTabBarContextMenu.swift */; }; @@ -938,7 +939,6 @@ 613DF55D2B08DD5D00E9D902 /* FileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = ""; }; 61538B8F2B111FE800A88846 /* String+AppearancesOfSubstring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AppearancesOfSubstring.swift"; sourceTree = ""; }; 61538B922B11201900A88846 /* String+Character.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Character.swift"; sourceTree = ""; }; - 615AA2192B0CFD480013FCCC /* LazyStringLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyStringLoader.swift; sourceTree = ""; }; 617DB3CF2C25AFAE00B58BFE /* TaskNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskNotificationHandler.swift; sourceTree = ""; }; 617DB3D22C25AFEA00B58BFE /* TaskNotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskNotificationModel.swift; sourceTree = ""; }; 617DB3D52C25B02D00B58BFE /* TaskNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskNotificationView.swift; sourceTree = ""; }; @@ -1037,6 +1037,9 @@ 6CBA0D502A1BF524002C6FAA /* SegmentedControlImproved.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlImproved.swift; sourceTree = ""; }; 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Caption3.swift"; sourceTree = ""; }; 6CBE1CFA2B71DAA6003AC32E /* Loopable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Loopable.swift; sourceTree = ""; }; + 6CC17B502C43311900834E2C /* ProjectNavigatorViewController+NSOutlineViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+NSOutlineViewDataSource.swift"; sourceTree = ""; }; + 6CC17B522C43314000834E2C /* ProjectNavigatorViewController+NSOutlineViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+NSOutlineViewDelegate.swift"; sourceTree = ""; }; + 6CC17B5A2C44258700834E2C /* WindowControllerPropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowControllerPropertyWrapper.swift; sourceTree = ""; }; 6CC9E4B129B5669900C97388 /* Environment+ActiveEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ActiveEditor.swift"; sourceTree = ""; }; 6CD03B6929FC773F001BD1D0 /* SettingsInjector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInjector.swift; sourceTree = ""; }; 6CDA84AC284C1BA000C1CC3A /* EditorTabBarContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarContextMenu.swift; sourceTree = ""; }; @@ -1206,9 +1209,9 @@ 6C85BB402C2105ED00EB5DEF /* CodeEditKit in Frameworks */, 6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */, 58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */, - 6CBE1D002B720565003AC32E /* CodeEditSourceEditor in Frameworks */, 6C147C4529A329350089B630 /* OrderedCollections in Frameworks */, 6C0617D62BDB4432008C9C42 /* LogStream in Frameworks */, + 6CC17B4F2C432AE000834E2C /* CodeEditSourceEditor in Frameworks */, 30CB64912C16CA8100CC8A9E /* LanguageServerProtocol in Frameworks */, 6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */, 6C85BB442C210EFD00EB5DEF /* SwiftUIIntrospect in Frameworks */, @@ -1216,6 +1219,7 @@ 5879828A292ED15F0085B254 /* SwiftTerm in Frameworks */, 2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */, 30CB64942C16CA9100CC8A9E /* LanguageClient in Frameworks */, + 6CC17B592C43F53700834E2C /* CodeEditSourceEditor in Frameworks */, 6C6BD6F829CD14D100235D17 /* CodeEditKit in Frameworks */, 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */, ); @@ -1242,20 +1246,10 @@ 043C321227E31FE8006AE443 /* Documents */ = { isa = PBXGroup; children = ( + 6CC17B552C4344F100834E2C /* CodeFileDocument */, 5831E3CE2933F3DE00D5A6D2 /* Controllers */, 611191F82B08CC8000D4459B /* Indexer */, - 58798249292E78D80085B254 /* CodeFileDocument.swift */, - 6C48B5C42C0A2835001E9955 /* FileEncoding.swift */, - 043C321527E3201F006AE443 /* WorkspaceDocument.swift */, - 043BCF02281DA18A000AC47C /* WorkspaceDocument+SearchState.swift */, - 61A53A802B4449F00093BF8A /* WorkspaceDocument+Index.swift */, - 61A53A7D2B4449870093BF8A /* WorkspaceDocument+Find.swift */, - 610C0FD92B44438F00A01CA7 /* WorkspaceDocument+FindAndReplace.swift */, - 615AA2192B0CFD480013FCCC /* LazyStringLoader.swift */, - 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */, - 6C092EDF2A53BFCF00489202 /* WorkspaceStateKey.swift */, - 61538B8F2B111FE800A88846 /* String+AppearancesOfSubstring.swift */, - 61538B922B11201900A88846 /* String+Character.swift */, + 6CC17B542C43448C00834E2C /* WorkspaceDocument */, ); path = Documents; sourceTree = ""; @@ -1372,8 +1366,8 @@ children = ( 2847019D27FDDF7600F87B6B /* ProjectNavigatorOutlineView.swift */, 285FEC6D27FE4B4A00E57D53 /* ProjectNavigatorViewController.swift */, - 6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */, - 6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */, + 6CC17B522C43314000834E2C /* ProjectNavigatorViewController+NSOutlineViewDelegate.swift */, + 6CC17B502C43311900834E2C /* ProjectNavigatorViewController+NSOutlineViewDataSource.swift */, 285FEC6F27FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift */, 285FEC7127FE4EEF00E57D53 /* ProjectNavigatorMenu.swift */, D7DC4B75298FFBE900D6C83D /* ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift */, @@ -2361,18 +2355,18 @@ 58D01C87293167DC00C5B6B4 /* Extensions */ = { isa = PBXGroup; children = ( - 669A504F2C380BFD00304CD8 /* Collection */, - 6C82D6C429C0129E00495C54 /* NSApplication */, - 77A01E922BCA9C0400F0EA38 /* NSWindow */, 588847672992AAB800996D95 /* Array */, - 6CBD1BC42978DE3E006639D5 /* Text */, - 5831E3D02934036D00D5A6D2 /* NSTableView */, - 5831E3CA2933E86F00D5A6D2 /* View */, 5831E3C72933E7F700D5A6D2 /* Bundle */, 5831E3C62933E7E600D5A6D2 /* Color */, + 669A504F2C380BFD00304CD8 /* Collection */, 5831E3C82933E80500D5A6D2 /* Date */, + 6C82D6C429C0129E00495C54 /* NSApplication */, + 5831E3D02934036D00D5A6D2 /* NSTableView */, + 77A01E922BCA9C0400F0EA38 /* NSWindow */, 58D01C8B293167DC00C5B6B4 /* String */, 5831E3CB2933E89A00D5A6D2 /* SwiftTerm */, + 6CBD1BC42978DE3E006639D5 /* Text */, + 5831E3CA2933E86F00D5A6D2 /* View */, ); path = Extensions; sourceTree = ""; @@ -2380,13 +2374,15 @@ 58D01C8B293167DC00C5B6B4 /* String */ = { isa = PBXGroup; children = ( + 61538B8F2B111FE800A88846 /* String+AppearancesOfSubstring.swift */, + 61538B922B11201900A88846 /* String+Character.swift */, + 669A50502C380C1800304CD8 /* String+escapedWhiteSpaces.swift */, + 85745D622A38F8D900089AAB /* String+HighlightOccurrences.swift */, + 6CED16E32A3E660D000EC962 /* String+Lines.swift */, 58D01C8E293167DC00C5B6B4 /* String+MD5.swift */, D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */, - 6CED16E32A3E660D000EC962 /* String+Lines.swift */, 58D01C8D293167DC00C5B6B4 /* String+RemoveOccurrences.swift */, 58D01C8C293167DC00C5B6B4 /* String+SHA256.swift */, - 85745D622A38F8D900089AAB /* String+HighlightOccurrences.swift */, - 669A50502C380C1800304CD8 /* String+escapedWhiteSpaces.swift */, ); path = String; sourceTree = ""; @@ -2602,6 +2598,15 @@ path = Models; sourceTree = ""; }; + 6C01F25D2C4820B600AA951B /* Recovered References */ = { + isa = PBXGroup; + children = ( + 6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */, + 6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */, + ); + name = "Recovered References"; + sourceTree = ""; + }; 6C092EDC2A53A63E00489202 /* Views */ = { isa = PBXGroup; children = ( @@ -2713,6 +2718,7 @@ children = ( B66A4E4429C8E86D004573B4 /* CommandsFixes.swift */, 6C82D6BB29C00CD900495C54 /* FirstResponderPropertyWrapper.swift */, + 6CC17B5A2C44258700834E2C /* WindowControllerPropertyWrapper.swift */, ); path = Utils; sourceTree = ""; @@ -2784,6 +2790,29 @@ path = Text; sourceTree = ""; }; + 6CC17B542C43448C00834E2C /* WorkspaceDocument */ = { + isa = PBXGroup; + children = ( + 043C321527E3201F006AE443 /* WorkspaceDocument.swift */, + 043BCF02281DA18A000AC47C /* WorkspaceDocument+SearchState.swift */, + 61A53A802B4449F00093BF8A /* WorkspaceDocument+Index.swift */, + 61A53A7D2B4449870093BF8A /* WorkspaceDocument+Find.swift */, + 610C0FD92B44438F00A01CA7 /* WorkspaceDocument+FindAndReplace.swift */, + 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */, + 6C092EDF2A53BFCF00489202 /* WorkspaceStateKey.swift */, + ); + path = WorkspaceDocument; + sourceTree = ""; + }; + 6CC17B552C4344F100834E2C /* CodeFileDocument */ = { + isa = PBXGroup; + children = ( + 58798249292E78D80085B254 /* CodeFileDocument.swift */, + 6C48B5C42C0A2835001E9955 /* FileEncoding.swift */, + ); + path = CodeFileDocument; + sourceTree = ""; + }; 77A01E1A2BB33F1E00F0EA38 /* Views */ = { isa = PBXGroup; children = ( @@ -2925,6 +2954,7 @@ 283BDCBC2972EEBD002AFF81 /* Package.resolved */, B658FB2D27DA9E0F00EA4DBD /* Products */, 5C403B8D27E20F8000788241 /* Frameworks */, + 6C01F25D2C4820B600AA951B /* Recovered References */, ); indentWidth = 4; sourceTree = ""; @@ -3365,10 +3395,11 @@ 6C6BD6F729CD14D100235D17 /* CodeEditKit */, 6C66C31229D05CDC00DE9ED2 /* GRDB */, 6CB4463F2B6DFF3A00539ED0 /* CodeEditSourceEditor */, - 6CBE1CFF2B720565003AC32E /* CodeEditSourceEditor */, 6C0617D52BDB4432008C9C42 /* LogStream */, 6C85BB3F2C2105ED00EB5DEF /* CodeEditKit */, 6C85BB432C210EFD00EB5DEF /* SwiftUIIntrospect */, + 6CC17B4E2C432AE000834E2C /* CodeEditSourceEditor */, + 6CC17B582C43F53700834E2C /* CodeEditSourceEditor */, ); productName = CodeEdit; productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */; @@ -3460,12 +3491,12 @@ 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */, 6C6BD6F229CD142C00235D17 /* XCRemoteSwiftPackageReference "collectionconcurrencykit" */, 6C66C31129D05CC800DE9ED2 /* XCRemoteSwiftPackageReference "GRDB.swift" */, - 6CBE1CFE2B720565003AC32E /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */, 6C0617D42BDB4432008C9C42 /* XCRemoteSwiftPackageReference "LogStream" */, 6C85BB3E2C2105ED00EB5DEF /* XCRemoteSwiftPackageReference "CodeEditKit" */, 6C85BB422C210EFD00EB5DEF /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, 303E88452C276FD100EEA8D9 /* XCRemoteSwiftPackageReference "LanguageClient" */, 303E88462C276FD600EEA8D9 /* XCRemoteSwiftPackageReference "LanguageServerProtocol" */, + 6CC17B572C43F53700834E2C /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */, ); productRefGroup = B658FB2D27DA9E0F00EA4DBD /* Products */; projectDirPath = ""; @@ -3689,6 +3720,7 @@ 6CED16E42A3E660D000EC962 /* String+Lines.swift in Sources */, 587B9E6B29301D8F00AC7927 /* GitLabAvatarURL.swift in Sources */, 58798233292E30B90085B254 /* FeedbackToolbar.swift in Sources */, + 6CC17B5B2C44258700834E2C /* WindowControllerPropertyWrapper.swift in Sources */, 587B9E6829301D8F00AC7927 /* GitLabAccountModel.swift in Sources */, 5878DAA7291AE76700DD95A3 /* OpenQuicklyViewModel.swift in Sources */, 6CFF967429BEBCC300182D6F /* FindCommands.swift in Sources */, @@ -3715,7 +3747,6 @@ 58798237292E30B90085B254 /* FeedbackView.swift in Sources */, 587B9E9829301D8F00AC7927 /* GitCommit.swift in Sources */, 6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */, - 6C67413E2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift in Sources */, 587B9E9429301D8F00AC7927 /* BitBucketTokenConfiguration.swift in Sources */, 04BA7C222AE2D95E00584E1C /* GitClient+CommitHistory.swift in Sources */, 581BFB672926431000D251EC /* WelcomeWindowView.swift in Sources */, @@ -3743,6 +3774,7 @@ B60718372B170638009CDAB4 /* SourceControlNavigatorRenameBranchView.swift in Sources */, 6C578D8129CD294800DC73B2 /* ExtensionActivatorView.swift in Sources */, B6F0517D29D9E4B100D72287 /* TerminalSettingsView.swift in Sources */, + 6CC17B532C43314000834E2C /* ProjectNavigatorViewController+NSOutlineViewDelegate.swift in Sources */, 587B9E8C29301D8F00AC7927 /* GitHubOpenness.swift in Sources */, 5894E59729FEF7740077E59C /* CEWorkspaceFile+Recursion.swift in Sources */, 9D36E1BF2B5E7D7500443C41 /* GitBranchesGroup.swift in Sources */, @@ -3811,6 +3843,7 @@ 613DF55E2B08DD5D00E9D902 /* FileHelper.swift in Sources */, 58798235292E30B90085B254 /* FeedbackModel.swift in Sources */, 04C3255C2801F86900C8DA2D /* ProjectNavigatorMenu.swift in Sources */, + 6CC17B512C43311900834E2C /* ProjectNavigatorViewController+NSOutlineViewDataSource.swift in Sources */, 587B9E6429301D8F00AC7927 /* GitLabCommit.swift in Sources */, B6E55C3B2A95368E003ECC7D /* EditorTabsOverflowShadow.swift in Sources */, 58A5DFA229339F6400D1BD5D /* KeybindingManager.swift in Sources */, @@ -3919,7 +3952,6 @@ 6C049A372A49E2DB00D42923 /* DirectoryEventStream.swift in Sources */, 30B088062C0D53080063A882 /* LanguageServer+DocumentSymbol.swift in Sources */, 04BA7C0E2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift in Sources */, - 615AA21A2B0CFD480013FCCC /* LazyStringLoader.swift in Sources */, 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */, 30AB4EBD2BF71CA800ED4431 /* DeveloperSettingsView.swift in Sources */, 6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */, @@ -4069,7 +4101,6 @@ 77A01E302BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift in Sources */, 669A50532C380C8E00304CD8 /* Collection+subscript_safe.swift in Sources */, 77A01E2C2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift in Sources */, - 6C6741402C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift in Sources */, 5B241BF32B6DDBFF0016E616 /* IgnorePatternListItemView.swift in Sources */, 6CB52DC92AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift in Sources */, 58F2EB0B292FB2B0004A9BDE /* AccountsSettings.swift in Sources */, @@ -5309,12 +5340,12 @@ minimumVersion = 1.2.0; }; }; - 6CBE1CFE2B720565003AC32E /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */ = { + 6CC17B572C43F53700834E2C /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/CodeEditApp/CodeEditSourceEditor"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.7.3; + minimumVersion = 0.7.4; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -5398,9 +5429,13 @@ isa = XCSwiftPackageProductDependency; productName = CodeEditSourceEditor; }; - 6CBE1CFF2B720565003AC32E /* CodeEditSourceEditor */ = { + 6CC17B4E2C432AE000834E2C /* CodeEditSourceEditor */ = { + isa = XCSwiftPackageProductDependency; + productName = CodeEditSourceEditor; + }; + 6CC17B582C43F53700834E2C /* CodeEditSourceEditor */ = { isa = XCSwiftPackageProductDependency; - package = 6CBE1CFE2B720565003AC32E /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */; + package = 6CC17B572C43F53700834E2C /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */; productName = CodeEditSourceEditor; }; /* End XCSwiftPackageProductDependency section */ diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b5dee3690..6bc620d0a 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "f3dd40f19b32a2b522dee7a1e94ceb60afbdb1a028938883adffc7328ef9d804", + "originHash" : "a33fcca819dee4c816b1474e19017510b1d62b170c921187042e0675d3f4b0b3", "pins" : [ { "identity" : "anycodable", @@ -31,10 +31,10 @@ { "identity" : "codeeditsourceeditor", "kind" : "remoteSourceControl", - "location" : "https://github.com/CodeEditApp/CodeEditSourceEditor.git", + "location" : "https://github.com/CodeEditApp/CodeEditSourceEditor", "state" : { - "revision" : "cf85789d527d569e94edfd674c5ac8071b244dd9", - "version" : "0.7.3" + "revision" : "4e014f71d7be053ea8d05f6c0e45be268f9a0d64", + "version" : "0.7.4" } }, { @@ -136,15 +136,6 @@ "version" : "1.3.0" } }, - { - "identity" : "mainoffender", - "kind" : "remoteSourceControl", - "location" : "https://github.com/mattmassicotte/MainOffender", - "state" : { - "revision" : "8de872d9256ff7f9913cbc5dd560568ab164be45", - "version" : "0.2.1" - } - }, { "identity" : "processenv", "kind" : "remoteSourceControl", @@ -258,8 +249,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ChimeHQ/TextFormation", "state" : { - "revision" : "f6faed6abd768ae95b70d10113d4008a7cac57a7", - "version" : "0.8.2" + "revision" : "b1ce9a14bd86042bba4de62236028dc4ce9db6a1", + "version" : "0.9.0" } }, { @@ -267,8 +258,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ChimeHQ/TextStory", "state" : { - "revision" : "8883fa739aa213e70e6cb109bfbf0a0b551e4cb5", - "version" : "0.8.0" + "revision" : "8dc9148b46fcf93b08ea9d4ef9bdb5e4f700e008", + "version" : "0.9.0" } } ], diff --git a/CodeEdit/AppDelegate.swift b/CodeEdit/AppDelegate.swift index c9da64926..e86b55a79 100644 --- a/CodeEdit/AppDelegate.swift +++ b/CodeEdit/AppDelegate.swift @@ -13,7 +13,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { private let updater = SoftwareUpdater() @Environment(\.openWindow) - private var openWindow + var openWindow func applicationDidFinishLaunching(_ notification: Notification) { setupServiceContainer() @@ -209,8 +209,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { defaults.removeObject(forKey: "openInCEFiles") } - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - self.checkForFilesToOpen() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + self?.checkForFilesToOpen() } } diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift index 3596a944b..53606c219 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift @@ -85,11 +85,11 @@ final class CEWorkspaceFile: Codable, Comparable, Hashable, Identifiable, Editor /// Returns a parent ``CEWorkspaceFile``. /// /// If the item already is the top-level ``CEWorkspaceFile`` this returns `nil`. - var parent: CEWorkspaceFile? + weak var parent: CEWorkspaceFile? private let fileDocumentSubject = PassthroughSubject() - var fileDocument: CodeFileDocument? { + weak var fileDocument: CodeFileDocument? { didSet { fileDocumentSubject.send(fileDocument) } diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift index 56453a092..42bfd1d46 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager.swift @@ -387,6 +387,7 @@ final class CEWorkspaceFileManager { } deinit { + fsEventStream?.cancel() observers.removeAllObjects() } } diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift index f3d20687d..67cefa3c1 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift @@ -10,20 +10,12 @@ import Combine /// The CodeEdit workspace settings model. final class CEWorkspaceSettings: ObservableObject { - @ObservedObject private var workspace: WorkspaceDocument @Published public var preferences: CEWorkspaceSettingsData = .init() - private var storeTask: AnyCancellable! + private var storeTask: AnyCancellable? private let fileManager = FileManager.default - private var folderURL: URL? { - guard let workspaceURL = workspace.fileURL else { - return nil - } - - return workspaceURL - .appendingPathComponent(".codeedit", isDirectory: true) - } + private(set) var folderURL: URL? private var settingsURL: URL? { folderURL? @@ -32,8 +24,7 @@ final class CEWorkspaceSettings: ObservableObject { } init(workspaceDocument: WorkspaceDocument) { - self.workspace = workspaceDocument - + folderURL = workspaceDocument.fileURL?.appendingPathComponent(".codeedit", isDirectory: true) loadSettings() self.storeTask = self.$preferences.throttle(for: 2.0, scheduler: RunLoop.main, latest: true).sink { @@ -41,6 +32,15 @@ final class CEWorkspaceSettings: ObservableObject { } } + func cleanUp() { + storeTask?.cancel() + storeTask = nil + } + + deinit { + cleanUp() + } + /// Load and construct ``CEWorkspaceSettings`` model from `.codeedit/settings.json` private func loadSettings() { if let settingsURL = settingsURL { diff --git a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift index 6e17ba2b7..db50b1b63 100644 --- a/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift +++ b/CodeEdit/Features/CodeEditUI/Views/ToolbarBranchPicker.swift @@ -11,8 +11,8 @@ import Combine /// A view that pops up a branch picker. struct ToolbarBranchPicker: View { - private var workspaceFileManager: CEWorkspaceFileManager? - private var sourceControlManager: SourceControlManager? + private weak var workspaceFileManager: CEWorkspaceFileManager? + private weak var sourceControlManager: SourceControlManager? @Environment(\.controlActiveState) private var controlActive diff --git a/CodeEdit/Features/Commands/Views/QuickActionsView.swift b/CodeEdit/Features/Commands/Views/QuickActionsView.swift index bf8e465fe..12d60e114 100644 --- a/CodeEdit/Features/Commands/Views/QuickActionsView.swift +++ b/CodeEdit/Features/Commands/Views/QuickActionsView.swift @@ -31,7 +31,7 @@ struct QuickActionsView: View { func callHandler(command: Command) { closePalette() - command.closureWrapper.call() + command.closureWrapper() selectedItem = nil state.commandQuery = "" state.filteredCommands = [] diff --git a/CodeEdit/Features/Documents/CodeFileDocument.swift b/CodeEdit/Features/Documents/CodeFileDocument/CodeFileDocument.swift similarity index 100% rename from CodeEdit/Features/Documents/CodeFileDocument.swift rename to CodeEdit/Features/Documents/CodeFileDocument/CodeFileDocument.swift diff --git a/CodeEdit/Features/Documents/FileEncoding.swift b/CodeEdit/Features/Documents/CodeFileDocument/FileEncoding.swift similarity index 100% rename from CodeEdit/Features/Documents/FileEncoding.swift rename to CodeEdit/Features/Documents/CodeFileDocument/FileEncoding.swift diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift index af83e56ac..a9b122bd0 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditSplitViewController.swift @@ -14,8 +14,8 @@ final class CodeEditSplitViewController: NSSplitViewController { static let snapWidth: CGFloat = 272 static let minSnapWidth: CGFloat = snapWidth - 10 - private var workspace: WorkspaceDocument - private var navigatorViewModel: NavigatorSidebarViewModel + private weak var workspace: WorkspaceDocument? + private weak var navigatorViewModel: NavigatorSidebarViewModel? private weak var windowRef: NSWindow? private unowned var hapticPerformer: NSHapticFeedbackPerformer @@ -47,12 +47,20 @@ final class CodeEditSplitViewController: NSSplitViewController { return } + guard let workspace, + let navigatorViewModel, + let editorManager = workspace.editorManager, + let statusBarViewModel = workspace.statusBarViewModel, + let utilityAreaModel = workspace.utilityAreaModel else { + return + } + splitView.translatesAutoresizingMaskIntoConstraints = false let settingsView = SettingsInjector { NavigatorAreaView(workspace: workspace, viewModel: navigatorViewModel) .environmentObject(workspace) - .environmentObject(workspace.editorManager) + .environmentObject(editorManager) } let navigator = NSSplitViewItem(sidebarWithViewController: NSHostingController(rootView: settingsView)) @@ -64,12 +72,12 @@ final class CodeEditSplitViewController: NSSplitViewController { addSplitViewItem(navigator) let workspaceView = SettingsInjector { - WindowObserver(window: windowRef) { + WindowObserver(window: WindowBox(value: windowRef)) { WorkspaceView() .environmentObject(workspace) - .environmentObject(workspace.editorManager) - .environmentObject(workspace.statusBarViewModel) - .environmentObject(workspace.utilityAreaModel) + .environmentObject(editorManager) + .environmentObject(statusBarViewModel) + .environmentObject(utilityAreaModel) } } @@ -82,7 +90,7 @@ final class CodeEditSplitViewController: NSSplitViewController { let inspectorView = SettingsInjector { InspectorAreaView(viewModel: InspectorAreaViewModel()) .environmentObject(workspace) - .environmentObject(workspace.editorManager) + .environmentObject(editorManager) } let inspector = NSSplitViewItem(inspectorWithViewController: NSHostingController(rootView: inspectorView)) @@ -98,6 +106,8 @@ final class CodeEditSplitViewController: NSSplitViewController { override func viewWillAppear() { super.viewWillAppear() + guard let workspace else { return } + let navigatorWidth = workspace.getFromWorkspaceState(.splitViewWidth) as? CGFloat splitView.setPosition(navigatorWidth ?? Self.minSidebarWidth, ofDividerAt: 0) @@ -178,16 +188,16 @@ final class CodeEditSplitViewController: NSSplitViewController { let panel = splitView.subviews[0] let width = panel.frame.size.width if width > 0 { - workspace.addToWorkspaceState(key: .splitViewWidth, value: width) + workspace?.addToWorkspaceState(key: .splitViewWidth, value: width) } } } func saveNavigatorCollapsedState(isCollapsed: Bool) { - workspace.addToWorkspaceState(key: .navigatorCollapsed, value: isCollapsed) + workspace?.addToWorkspaceState(key: .navigatorCollapsed, value: isCollapsed) } func saveInspectorCollapsedState(isCollapsed: Bool) { - workspace.addToWorkspaceState(key: .inspectorCollapsed, value: isCollapsed) + workspace?.addToWorkspaceState(key: .inspectorCollapsed, value: isCollapsed) } } diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index ec9131dac..7878cbd2c 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -9,7 +9,7 @@ import Cocoa import SwiftUI import Combine -final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, ObservableObject { +final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, ObservableObject, NSWindowDelegate { @Published var navigatorCollapsed = false @Published var inspectorCollapsed = false @Published var toolbarCollapsed = false @@ -25,10 +25,12 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs var taskNotificationHandler: TaskNotificationHandler - var splitViewController: NSSplitViewController! - internal var cancellables = [AnyCancellable]() + var splitViewController: CodeEditSplitViewController? { + contentViewController as? CodeEditSplitViewController + } + init( window: NSWindow?, workspace: WorkspaceDocument?, @@ -36,10 +38,13 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs ) { self.taskNotificationHandler = taskNotificationHandler super.init(window: window) + window?.delegate = self guard let workspace else { return } self.workspace = workspace self.workspaceSettings = CEWorkspaceSettings(workspaceDocument: workspace) - setupSplitView(with: workspace) + guard let splitViewController = setupSplitView(with: workspace) else { + fatalError("Failed to set up content view.") + } // Previous: // An NSHostingController is used, so the root viewController of the window is a SwiftUI-managed one. @@ -69,37 +74,40 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs registerCommands() } - deinit { cancellables.forEach({ $0.cancel() }) } + deinit { + cancellables.forEach({ $0.cancel() }) + cancellables.removeAll() + } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - private func setupSplitView(with workspace: WorkspaceDocument) { + private func setupSplitView(with workspace: WorkspaceDocument) -> CodeEditSplitViewController? { guard let window else { assertionFailure("No window found for this controller. Cannot set up content.") - return + return nil } let navigatorModel = NavigatorSidebarViewModel() navigatorSidebarViewModel = navigatorModel - self.splitViewController = CodeEditSplitViewController( + self.listenToDocumentEdited(workspace: workspace) + return CodeEditSplitViewController( workspace: workspace, navigatorViewModel: navigatorModel, windowRef: window ) - self.listenToDocumentEdited(workspace: workspace) } private func getSelectedCodeFile() -> CodeFileDocument? { - workspace?.editorManager.activeEditor.selectedTab?.file.fileDocument + workspace?.editorManager?.activeEditor.selectedTab?.file.fileDocument } @IBAction func saveDocument(_ sender: Any) { guard let codeFile = getSelectedCodeFile() else { return } codeFile.save(sender) - workspace?.editorManager.activeEditor.temporaryTab = nil + workspace?.editorManager?.activeEditor.temporaryTab = nil } @IBAction func openCommandPalette(_ sender: Any) { @@ -125,7 +133,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs } } - @IBAction func openQuickly(_ sender: Any) { + @IBAction func openQuickly(_ sender: Any?) { if let workspace, let state = workspace.openQuicklyViewModel { if let quickOpenPanel { if quickOpenPanel.isKeyWindow { @@ -142,7 +150,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs let contentView = OpenQuicklyView(state: state) { panel.close() } openFile: { file in - workspace.editorManager.openTab(item: file) + workspace.editorManager?.openTab(item: file) }.environmentObject(workspace) panel.contentView = NSHostingView(rootView: SettingsInjector { contentView }) @@ -153,18 +161,41 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs } @IBAction func closeCurrentTab(_ sender: Any) { - if (workspace?.editorManager.activeEditor.tabs ?? []).isEmpty { + if (workspace?.editorManager?.activeEditor.tabs ?? []).isEmpty { self.closeActiveEditor(self) } else { - workspace?.editorManager.activeEditor.closeSelectedTab() + workspace?.editorManager?.activeEditor.closeSelectedTab() } } @IBAction func closeActiveEditor(_ sender: Any) { - if workspace?.editorManager.editorLayout.findSomeEditor(except: workspace?.editorManager.activeEditor) == nil { - NSApp.sendAction(#selector(NSWindow.close), to: nil, from: nil) + if workspace?.editorManager?.editorLayout.findSomeEditor( + except: workspace?.editorManager?.activeEditor + ) == nil { + NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: nil, from: nil) } else { - workspace?.editorManager.activeEditor.close() + workspace?.editorManager?.activeEditor.close() + } + } + + func windowShouldClose(_ sender: NSWindow) -> Bool { + cancellables.forEach({ $0.cancel() }) + cancellables.removeAll() + + for _ in 0..<(splitViewController?.children.count ?? 0) { + splitViewController?.removeChild(at: 0) } + contentViewController?.removeFromParent() + contentViewController = nil + + workspaceSettingsWindow?.close() + workspaceSettingsWindow = nil + workspaceSettings?.cleanUp() + workspaceSettings = nil + quickOpenPanel = nil + commandPalettePanel = nil + navigatorSidebarViewModel = nil + workspace = nil + return true } } diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift index f1a858615..5d556257e 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift @@ -11,17 +11,14 @@ import Combine extension CodeEditWindowController { @objc func toggleFirstPanel() { - guard let firstSplitView = splitViewController.splitViewItems.first else { return } + guard let firstSplitView = splitViewController?.splitViewItems.first else { return } firstSplitView.animator().isCollapsed.toggle() - if let codeEditSplitVC = splitViewController as? CodeEditSplitViewController { - codeEditSplitVC.saveNavigatorCollapsedState(isCollapsed: firstSplitView.isCollapsed) - } + splitViewController?.saveNavigatorCollapsedState(isCollapsed: firstSplitView.isCollapsed) } @objc func toggleLastPanel() { - guard let lastSplitView = splitViewController.splitViewItems.last, - let codeEditSplitVC = splitViewController as? CodeEditSplitViewController else { + guard let lastSplitView = splitViewController?.splitViewItems.last else { return } @@ -29,7 +26,7 @@ extension CodeEditWindowController { lastSplitView.animator().isCollapsed.toggle() } - codeEditSplitVC.saveInspectorCollapsedState(isCollapsed: lastSplitView.isCollapsed) + splitViewController?.saveInspectorCollapsedState(isCollapsed: lastSplitView.isCollapsed) } /// These are example items that added as commands to command palette @@ -38,27 +35,27 @@ extension CodeEditWindowController { name: "Quick Open", title: "Quick Open", id: "quick_open", - command: CommandClosureWrapper(closure: { self.openQuickly(self) }) + command: { [weak self] in self?.openQuickly(nil) } ) CommandManager.shared.addCommand( name: "Toggle Navigator", title: "Toggle Navigator", id: "toggle_left_sidebar", - command: CommandClosureWrapper(closure: { self.toggleFirstPanel() }) + command: { [weak self] in self?.toggleFirstPanel() } ) CommandManager.shared.addCommand( name: "Toggle Inspector", title: "Toggle Inspector", id: "toggle_right_sidebar", - command: CommandClosureWrapper(closure: { self.toggleLastPanel() }) + command: { [weak self] in self?.toggleLastPanel() } ) } // Listen to changes in all tabs/files internal func listenToDocumentEdited(workspace: WorkspaceDocument) { - workspace.editorManager.$activeEditor + workspace.editorManager?.$activeEditor .flatMap({ editor in editor.$tabs }) @@ -82,7 +79,7 @@ extension CodeEditWindowController { // Listen to change of tabs, if closed tab without saving content, // we also need to recalculate isDocumentEdited - workspace.editorManager.$activeEditor + workspace.editorManager?.$activeEditor .flatMap({ editor in editor.$tabs }) @@ -94,12 +91,12 @@ extension CodeEditWindowController { // Recalculate documentEdited by checking if any tab/file is edited private func updateDocumentEdited(workspace: WorkspaceDocument) { - let hasEditedDocuments = !workspace - .editorManager + let hasEditedDocuments = !(workspace + .editorManager? .editorLayout .gatherOpenFiles() .filter({ $0.fileDocument?.isDocumentEdited == true }) - .isEmpty + .isEmpty ?? true) self.setDocumentEdited(hasEditedDocuments) } diff --git a/CodeEdit/Features/Documents/LazyStringLoader.swift b/CodeEdit/Features/Documents/LazyStringLoader.swift deleted file mode 100644 index e2b82b649..000000000 --- a/CodeEdit/Features/Documents/LazyStringLoader.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// LazyStringLoader.swift -// CodeEdit -// -// Created by Tommy Ludwig on 21.11.23. -// - -import Foundation - -class LazyStringLoader { - let fileURL: URL - var fileHandle: FileHandle? - let chunkSize: Int - let queue = DispatchQueue(label: "com.CodeEdit.LazyLoader") - - init(fileURL: URL, chunkSize: Int = 1024) { - self.fileURL = fileURL - self.chunkSize = chunkSize - } - - func getNextChunk() -> String? { - if fileHandle == nil { - do { - fileHandle = try FileHandle(forReadingFrom: fileURL) - guard let data = try fileHandle?.read(upToCount: chunkSize) else { - return nil - } - return String(decoding: data, as: UTF8.self) - } catch { - return nil - } - } - return nil - } -} diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+Find.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Find.swift similarity index 100% rename from CodeEdit/Features/Documents/WorkspaceDocument+Find.swift rename to CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Find.swift diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+FindAndReplace.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+FindAndReplace.swift similarity index 100% rename from CodeEdit/Features/Documents/WorkspaceDocument+FindAndReplace.swift rename to CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+FindAndReplace.swift diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+Index.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Index.swift similarity index 100% rename from CodeEdit/Features/Documents/WorkspaceDocument+Index.swift rename to CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Index.swift diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+Listeners.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Listeners.swift similarity index 100% rename from CodeEdit/Features/Documents/WorkspaceDocument+Listeners.swift rename to CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Listeners.swift diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+SearchState.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+SearchState.swift similarity index 100% rename from CodeEdit/Features/Documents/WorkspaceDocument+SearchState.swift rename to CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+SearchState.swift diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift similarity index 88% rename from CodeEdit/Features/Documents/WorkspaceDocument.swift rename to CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift index f813e9b55..9f2859820 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift @@ -16,10 +16,6 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { @Published var sortFoldersOnTop: Bool = true - var workspaceFileManager: CEWorkspaceFileManager? - - var editorManager = EditorManager() - private var workspaceState: [String: Any] { get { let key = "workspaceState-\(self.fileURL?.absoluteString ?? "")" @@ -31,8 +27,11 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { } } - var statusBarViewModel = StatusBarViewModel() - var utilityAreaModel = UtilityAreaViewModel() + var workspaceFileManager: CEWorkspaceFileManager? + + var editorManager: EditorManager? = EditorManager() + var statusBarViewModel: StatusBarViewModel? = StatusBarViewModel() + var utilityAreaModel: UtilityAreaViewModel? = UtilityAreaViewModel() var searchState: SearchState? var openQuicklyViewModel: OpenQuicklyViewModel? var commandsPaletteState: QuickActionsViewModel? @@ -102,6 +101,8 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { window.center() } self.addWindowController(windowController) + + window.makeKeyAndOrderFront(nil) } // MARK: Set Up Workspace @@ -112,7 +113,7 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { let sourceControlManager = SourceControlManager( workspaceURL: url, - editorManager: editorManager + editorManager: editorManager! ) self.workspaceFileManager = .init( @@ -126,8 +127,8 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { self.openQuicklyViewModel = .init(fileURL: url) self.commandsPaletteState = .init() - editorManager.restoreFromState(self) - utilityAreaModel.restoreFromState(self) + editorManager?.restoreFromState(self) + utilityAreaModel?.restoreFromState(self) } override func read(from url: URL, ofType typeName: String) throws { @@ -139,9 +140,20 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { // MARK: Close Workspace override func close() { - editorManager.saveRestorationState(self) - utilityAreaModel.saveRestorationState(self) super.close() + editorManager?.saveRestorationState(self) + utilityAreaModel?.saveRestorationState(self) + + cancellables.forEach({ $0.cancel() }) + statusBarViewModel = nil + utilityAreaModel = nil + searchState = nil + editorManager = nil + openQuicklyViewModel = nil + commandsPaletteState = nil + sourceControlManager = nil + workspaceFileManager?.cleanUp() + workspaceFileManager = nil } /// Determines the windows should be closed. @@ -178,10 +190,10 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { return } // Save unsaved changes before closing - let editedCodeFiles = editorManager.editorLayout + let editedCodeFiles = editorManager?.editorLayout .gatherOpenFiles() .compactMap(\.fileDocument) - .filter(\.isDocumentEdited) + .filter(\.isDocumentEdited) ?? [] for editedCodeFile in editedCodeFiles { let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) @@ -207,9 +219,9 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { implementation, to: (@convention(c)(Any, Selector, Any, Bool, UnsafeMutableRawPointer?) -> Void).self ) - let areAllOpenedCodeFilesClean = editorManager.editorLayout.gatherOpenFiles() + let areAllOpenedCodeFilesClean = editorManager?.editorLayout.gatherOpenFiles() .compactMap(\.fileDocument) - .allSatisfy { !$0.isDocumentEdited } + .allSatisfy { !$0.isDocumentEdited } ?? false function(object, shouldCloseSelector, self, areAllOpenedCodeFilesClean, contextInfo) } diff --git a/CodeEdit/Features/Documents/WorkspaceStateKey.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceStateKey.swift similarity index 100% rename from CodeEdit/Features/Documents/WorkspaceStateKey.swift rename to CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceStateKey.swift diff --git a/CodeEdit/Features/Editor/Models/Editor.swift b/CodeEdit/Features/Editor/Models/Editor.swift index be1f434f0..e1fa6590c 100644 --- a/CodeEdit/Features/Editor/Models/Editor.swift +++ b/CodeEdit/Features/Editor/Models/Editor.swift @@ -123,6 +123,9 @@ final class Editor: ObservableObject, Identifiable { } // Reset change count to 0 file.fileDocument?.updateChangeCount(.changeCleared) + if let codeFile = file.fileDocument { + codeFile.close() + } // remove file from memory file.fileDocument = nil } diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarContextMenu.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarContextMenu.swift index 7732af00f..7393f4fff 100644 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarContextMenu.swift +++ b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarContextMenu.swift @@ -139,7 +139,7 @@ struct EditorTabBarContextMenu: ViewModifier { let newEditor = Editor(files: [item]) splitEditor(edge, newEditor) tabs.closeTab(file: item) - workspace.editorManager.activeEditor = newEditor + workspace.editorManager?.activeEditor = newEditor } /// Copies the relative path from the workspace folder to the given file item to the pasteboard. diff --git a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift index 0402aae00..2744a65d3 100644 --- a/CodeEdit/Features/Editor/Views/EditorLayoutView.swift +++ b/CodeEdit/Features/Editor/Views/EditorLayoutView.swift @@ -12,14 +12,14 @@ struct EditorLayoutView: View { @FocusState.Binding var focus: Editor? - @Environment(\.window) + @Environment(\.window.value) private var window @Environment(\.isAtEdge) private var isAtEdge var toolbarHeight: CGFloat { - window.contentView?.safeAreaInsets.top ?? .zero + window?.contentView?.safeAreaInsets.top ?? .zero } var body: some View { diff --git a/CodeEdit/Features/Extensions/ExtensionDiscovery.swift b/CodeEdit/Features/Extensions/ExtensionDiscovery.swift index 2eb309528..36c28e96d 100644 --- a/CodeEdit/Features/Extensions/ExtensionDiscovery.swift +++ b/CodeEdit/Features/Extensions/ExtensionDiscovery.swift @@ -25,30 +25,30 @@ final class ExtensionDiscovery: ObservableObject { /// These endpoints can be used to create new extension processes with XPC. @Published var extensions: [ExtensionInfo] = [] + private var discoverTask: Task? + private var availabilityTask: Task? + // Init is private as only 1 instance of this class may (needs to) exist. private init() { // Two separate tasks need to be used, as the awaits never finish. - Task { - await discover() - } - - Task { - await availabilityOverview() - } + discoverTask = discover() + availabilityTask = availabilityOverview() } /// Discover all the extensions approved by the user. Updates `extensions` when an extension gets enabled/disabled. /// Warning: This function will continue to run and won't return. Therefore, it should be ran in a separate `Task`. - private func discover() async { - print("Change in active extensions, reconnecting...") - do { - let sequence = try AppExtensionIdentity.matching(appExtensionPointIDs: Self.endPointIdentifier) + private func discover() -> Task { + Task { [weak self] in + do { + let sequence = try AppExtensionIdentity.matching(appExtensionPointIDs: Self.endPointIdentifier) - for await endpoints in sequence { - await updateExtensions(endpoints: endpoints, shouldRestartExisting: true) + for await endpoints in sequence { + guard !Task.isCancelled && self != nil else { return } + await self?.updateExtensions(endpoints: endpoints, shouldRestartExisting: true) + } + } catch { + print("Error while searching for extensions: \(error.localizedDescription)") } - } catch { - print("Error while searching for extensions: \(error.localizedDescription)") } } @@ -73,30 +73,33 @@ final class ExtensionDiscovery: ObservableObject { /// Observes extensions available on the system, and reports if extensions are disabled. /// These extensions must be enabled by the user first, before they can be discovered by `discover`. /// Warning: This function will continue to run and won't return. Therefore, it should be ran in a separate `Task`. - private func availabilityOverview() async { - for await availability in AppExtensionIdentity.availabilityUpdates { - print(availability) - do { - if availability.disabledCount > 0 { - print("Found \(availability.disabledCount) disabled extensions, trying to activate...") - try await activateDisabledExtensions() + private func availabilityOverview() -> Task { + Task { [weak self] in + for await availability in AppExtensionIdentity.availabilityUpdates { + guard !Task.isCancelled && self != nil else { return } + print(availability) + do { + if availability.disabledCount > 0 { + print("Found \(availability.disabledCount) disabled extensions, trying to activate...") + try await self?.activateDisabledExtensions() + } + + if availability.unapprovedCount > 0 { + print("Found \(availability.disabledCount) unapproved extensions, trying to activate...") + + let identifiers = [("com.tweety.TestCodeEdit.AutoActivatedExtension", "2MMGJGVTB4")] + try await self?.activateUnapprovedExtensions(with: identifiers) + } + + let sequence = try AppExtensionIdentity.matching(appExtensionPointIDs: Self.endPointIdentifier) + + let extensions = await sequence.first { _ in true } + + guard let extensions else { return } + await self?.updateExtensions(endpoints: extensions) + } catch { + print("Could not auto-activate extensions.") } - - if availability.unapprovedCount > 0 { - print("Found \(availability.disabledCount) unapproved extensions, trying to activate...") - - let identifiers = [("com.tweety.TestCodeEdit.AutoActivatedExtension", "2MMGJGVTB4")] - try await activateUnapprovedExtensions(with: identifiers) - } - - let sequence = try AppExtensionIdentity.matching(appExtensionPointIDs: Self.endPointIdentifier) - - let extensions = await sequence.first { _ in true } - - guard let extensions else { return } - await updateExtensions(endpoints: extensions) - } catch { - print("Could not auto-activate extensions.") } } } diff --git a/CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift b/CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift index b8d2c1c72..3707df1e8 100644 --- a/CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift +++ b/CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift @@ -95,8 +95,8 @@ struct FileInspectorView: View { if !file.isFolder { editorManager.editorLayout.closeAllTabs(of: file) } - DispatchQueue.main.async { - workspace.workspaceFileManager?.move(file: file, to: destinationURL) + DispatchQueue.main.async { [weak workspace] in + workspace?.workspaceFileManager?.move(file: file, to: destinationURL) let newItem = CEWorkspaceFile(url: destinationURL) newItem.parent = file.parent if !newItem.isFolder { @@ -139,10 +139,10 @@ struct FileInspectorView: View { } // This is ugly but if the tab is opened at the same time as closing the others, it doesn't open // And if the files are re-built at the same time as the tab is opened, it causes a memory error - DispatchQueue.main.async { - workspace.workspaceFileManager?.move(file: file, to: newURL) + DispatchQueue.main.async { [weak workspace] in + workspace?.workspaceFileManager?.move(file: file, to: newURL) // If the parent directory doesn't exist in the workspace, don't open it in a tab. - if let newParent = workspace.workspaceFileManager?.getFile( + if let newParent = workspace?.workspaceFileManager?.getFile( newURL.deletingLastPathComponent().path ) { let newItem = CEWorkspaceFile(url: newURL) @@ -150,8 +150,8 @@ struct FileInspectorView: View { if !file.isFolder { editorManager.openTab(item: newItem) } - DispatchQueue.main.async { - _ = try? workspace.workspaceFileManager?.rebuildFiles(fromItem: newParent) + DispatchQueue.main.async { [weak workspace] in + _ = try? workspace?.workspaceFileManager?.rebuildFiles(fromItem: newParent) } } } diff --git a/CodeEdit/Features/Keybindings/CommandManager.swift b/CodeEdit/Features/Keybindings/CommandManager.swift index 394f58494..f21f2ad81 100644 --- a/CodeEdit/Features/Keybindings/CommandManager.swift +++ b/CodeEdit/Features/Keybindings/CommandManager.swift @@ -31,7 +31,7 @@ final class CommandManager: ObservableObject { static let shared: CommandManager = .init() - func addCommand(name: String, title: String, id: String, command: CommandClosureWrapper) { + func addCommand(name: String, title: String, id: String, command: @escaping () -> Void) { let command = Command.init(id: name, title: title, closureWrapper: command) commandsList[id] = command } @@ -41,7 +41,7 @@ final class CommandManager: ObservableObject { } func executeCommand(_ id: String) { - commandsList[id]?.closureWrapper.call() + commandsList[id]?.closureWrapper() } } @@ -62,24 +62,5 @@ struct Command: Identifiable, Hashable { let id: String let title: String - let closureWrapper: CommandClosureWrapper -} - -/// A simple wrapper for command closure -struct CommandClosureWrapper { - - /// A typealias of interface used for command closure declaration - typealias WorkspaceClientClosure = () -> Void - - let workspaceClientClosure: WorkspaceClientClosure? - - /// Initializer for closure wrapper - /// - Parameter closure: Function that contains all logic to run command. - init(closure: @escaping WorkspaceClientClosure) { - self.workspaceClientClosure = closure - } - - func call() { - workspaceClientClosure?() - } + let closureWrapper: () -> Void } diff --git a/CodeEdit/Features/LSP/LSPEventHandler.swift b/CodeEdit/Features/LSP/LSPEventHandler.swift index 187edf8e4..ae6ee28b4 100644 --- a/CodeEdit/Features/LSP/LSPEventHandler.swift +++ b/CodeEdit/Features/LSP/LSPEventHandler.swift @@ -46,25 +46,25 @@ extension LSPService { // swiftlint:disable:next cyclomatic_complexity private func handleRequest(_ request: ServerRequest) { switch request { - case let .workspaceConfiguration(params, handler): + case let .workspaceConfiguration(params, _): print("workspaceConfiguration: \(params)") case let .workspaceFolders(handler): print("workspaceFolders: \(String(describing: handler))") - case let .workspaceApplyEdit(params, handler): + case let .workspaceApplyEdit(params, _): print("workspaceApplyEdit: \(params)") - case let .clientRegisterCapability(params, handler): + case let .clientRegisterCapability(params, _): print("clientRegisterCapability: \(params)") - case let .clientUnregisterCapability(params, handler): + case let .clientUnregisterCapability(params, _): print("clientUnregisterCapability: \(params)") case let .workspaceCodeLensRefresh(handler): print("workspaceCodeLensRefresh: \(String(describing: handler))") case let .workspaceSemanticTokenRefresh(handler): print("workspaceSemanticTokenRefresh: \(String(describing: handler))") - case let .windowShowMessageRequest(params, handler): + case let .windowShowMessageRequest(params, _): print("windowShowMessageRequest: \(params)") - case let .windowShowDocument(params, handler): + case let .windowShowDocument(params, _): print("windowShowDocument: \(params)") - case let .windowWorkDoneProgressCreate(params, handler): + case let .windowWorkDoneProgressCreate(params, _): print("windowWorkDoneProgressCreate: \(params)") default: diff --git a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift index 501686b72..f469de2ee 100644 --- a/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift +++ b/CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift @@ -181,7 +181,7 @@ extension FindNavigatorListViewController: NSOutlineViewDelegate { ) // We're using a medium label for file names b/c it makes it easier to // distinguish quickly which results are from which files. - view.label.font = .systemFont(ofSize: 13, weight: .medium) + view.textField?.font = .systemFont(ofSize: 13, weight: .medium) return view } } @@ -197,13 +197,13 @@ extension FindNavigatorListViewController: NSOutlineViewDelegate { let selectedMatch = self.selectedItem as? SearchResultMatchModel if selectedItem == nil || selectedMatch != item { self.selectedItem = item - workspace.editorManager.openTab(item: item.file) + workspace.editorManager?.openTab(item: item.file) } } else if let item = outlineView.item(atRow: selectedIndex) as? SearchResultModel { let selectedFile = self.selectedItem as? SearchResultModel if selectedItem == nil || selectedFile != item { self.selectedItem = item - workspace.editorManager.openTab(item: item.file) + workspace.editorManager?.openTab(item: item.file) } } } @@ -230,7 +230,6 @@ extension FindNavigatorListViewController: NSOutlineViewDelegate { let indexes = IndexSet(integersIn: 0.. = [] - var workspace: WorkspaceDocument - var controller: ProjectNavigatorViewController? + weak var workspace: WorkspaceDocument? + weak var controller: ProjectNavigatorViewController? func fileManagerUpdated(updatedItems: Set) { guard let outlineView = controller?.outlineView else { return } @@ -76,7 +76,7 @@ struct ProjectNavigatorOutlineView: NSViewControllerRepresentable { } deinit { - workspace.workspaceFileManager?.removeObserver(self) + workspace?.workspaceFileManager?.removeObserver(self) } } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift index 64dab481f..bd6421170 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift @@ -14,7 +14,7 @@ protocol OutlineTableViewCellDelegate: AnyObject { /// A `NSTableCellView` showing an ``icon`` and a ``label`` final class ProjectNavigatorTableViewCell: FileSystemTableViewCell { - private var delegate: OutlineTableViewCellDelegate? + private weak var delegate: OutlineTableViewCellDelegate? /// Initializes the `OutlineTableViewCell` with an `icon` and `label` /// Both the icon and label will be colored, and sized based on the user's preferences. @@ -29,7 +29,7 @@ final class ProjectNavigatorTableViewCell: FileSystemTableViewCell { delegate: OutlineTableViewCellDelegate? = nil ) { super.init(frame: frameRect, item: item, isEditable: isEditable) - self.label.setAccessibilityIdentifier("ProjectNavigatorTableViewCell-\(item?.name ?? "")") + self.textField?.setAccessibilityIdentifier("ProjectNavigatorTableViewCell-\(item?.name ?? "")") self.delegate = delegate } @@ -51,14 +51,15 @@ final class ProjectNavigatorTableViewCell: FileSystemTableViewCell { } override func controlTextDidEndEditing(_ obj: Notification) { - label.backgroundColor = fileItem.validateFileName(for: label?.stringValue ?? "") ? .none : errorRed - if fileItem.validateFileName(for: label?.stringValue ?? "") { + guard let fileItem else { return } + textField?.backgroundColor = fileItem.validateFileName(for: textField?.stringValue ?? "") ? .none : errorRed + if fileItem.validateFileName(for: textField?.stringValue ?? "") { let destinationURL = fileItem.url .deletingLastPathComponent() - .appendingPathComponent(label?.stringValue ?? "") + .appendingPathComponent(textField?.stringValue ?? "") delegate?.moveFile(file: fileItem, to: destinationURL) } else { - label?.stringValue = fileItem.labelFileName() + textField?.stringValue = fileItem.labelFileName() } } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+DataSource.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDataSource.swift similarity index 94% rename from CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+DataSource.swift rename to CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDataSource.swift index 1437f9c87..2b7fc81d7 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+DataSource.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDataSource.swift @@ -1,8 +1,8 @@ // -// ProjectNavigatorViewController+DataSource.swift +// ProjectNavigatorViewController+NSOutlineViewDataSource.swift // CodeEdit // -// Created by Khan Winter on 7/14/24. +// Created by Khan Winter on 7/13/24. // import AppKit @@ -30,13 +30,13 @@ extension ProjectNavigatorViewController: NSOutlineViewDataSource { return false } - /// write dragged file(s) to pasteboard + /// Write dragged file(s) to pasteboard func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? { guard let fileItem = item as? CEWorkspaceFile else { return nil } return fileItem.url as NSURL } - /// declare valid drop target + /// Declare valid drop target func outlineView( _ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, @@ -54,7 +54,7 @@ extension ProjectNavigatorViewController: NSOutlineViewDataSource { return [] } - /// handle successful or unsuccessful drop + /// Handle successful or unsuccessful drop func outlineView( _ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+Delegate.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift similarity index 86% rename from CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+Delegate.swift rename to CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift index 3389db81c..53d9298a6 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+Delegate.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift @@ -1,8 +1,8 @@ // -// ProjectNavigatorViewController+Delegate.swift +// ProjectNavigatorViewController+NSOutlineViewDelegate.swift // CodeEdit // -// Created by Khan Winter on 7/14/24. +// Created by Khan Winter on 7/13/24. // import AppKit @@ -36,10 +36,10 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate { guard let item = outlineView.item(atRow: selectedIndex) as? CEWorkspaceFile else { return } if !item.isFolder && shouldSendSelectionUpdate { - DispatchQueue.main.async { - self.shouldSendSelectionUpdate = false - self.workspace?.editorManager.activeEditor.openTab(file: item, asTemporary: true) - self.shouldSendSelectionUpdate = true + DispatchQueue.main.async { [weak self] in + self?.shouldSendSelectionUpdate = false + self?.workspace?.editorManager?.activeEditor.openTab(file: item, asTemporary: true) + self?.shouldSendSelectionUpdate = true } } } @@ -49,7 +49,7 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate { } func outlineViewItemDidExpand(_ notification: Notification) { - guard let id = workspace?.editorManager.activeEditor.selectedTab?.file.id, + guard let id = workspace?.editorManager?.activeEditor.selectedTab?.file.id, let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true), /// update outline selection only if the parent of selected item match with expanded item item.parent === notification.userInfo?["NSObject"] as? CEWorkspaceFile else { @@ -145,4 +145,19 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate { } outlineView.expandItem(item) } + + /// Adds a tooltip to the file row. + func outlineView( // swiftlint:disable:this function_parameter_count + _ outlineView: NSOutlineView, + toolTipFor cell: NSCell, + rect: NSRectPointer, + tableColumn: NSTableColumn?, + item: Any, + mouseLocation: NSPoint + ) -> String { + if let file = item as? CEWorkspaceFile { + return file.name + } + return "" + } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift index fbb3edd38..633d77093 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift @@ -12,11 +12,11 @@ import Foundation extension ProjectNavigatorViewController: OutlineTableViewCellDelegate { func moveFile(file: CEWorkspaceFile, to destination: URL) { if !file.isFolder { - workspace?.editorManager.editorLayout.closeAllTabs(of: file) + workspace?.editorManager?.editorLayout.closeAllTabs(of: file) } workspace?.workspaceFileManager?.move(file: file, to: destination) if !file.isFolder { - workspace?.editorManager.openTab(item: .init(url: destination)) + workspace?.editorManager?.openTab(item: .init(url: destination)) } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift index 89d58212a..37c1e2327 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift @@ -26,7 +26,7 @@ final class ProjectNavigatorViewController: NSViewController { return [root] } - var workspace: WorkspaceDocument? + weak var workspace: WorkspaceDocument? var iconColor: SettingsData.FileIconStyle = .color { willSet { @@ -95,6 +95,11 @@ final class ProjectNavigatorViewController: NSViewController { super.init(nibName: nil, bundle: nil) } + deinit { + outlineView?.removeFromSuperview() + scrollView?.removeFromSuperview() + } + required init?(coder: NSCoder) { fatalError() } @@ -102,7 +107,7 @@ final class ProjectNavigatorViewController: NSViewController { /// Forces to reveal the selected file through the command regardless of the auto reveal setting @objc func revealFile(_ sender: Any) { - updateSelection(itemID: workspace?.editorManager.activeEditor.selectedTab?.file.id, forcesReveal: true) + updateSelection(itemID: workspace?.editorManager?.activeEditor.selectedTab?.file.id, forcesReveal: true) } /// Updates the selection of the ``outlineView`` whenever it changes. @@ -130,7 +135,7 @@ final class ProjectNavigatorViewController: NSViewController { outlineView.expandItem(item) } } else if Settings[\.navigation].navigationStyle == .openInTabs { - workspace?.editorManager.activeEditor.openTab(file: item, asTemporary: false) + workspace?.editorManager?.activeEditor.openTab(file: item, asTemporary: false) } } diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesList.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesList.swift index 857dedd23..3adf7b835 100644 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesList.swift +++ b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Changes/Views/SourceControlNavigatorChangesList.swift @@ -37,7 +37,7 @@ struct SourceControlNavigatorChangesList: View { Group { Button("Open in New Tab") { DispatchQueue.main.async { - workspace.editorManager.openTab(item: file) + workspace.editorManager?.openTab(item: file) } } Button("Open in New Window") {} @@ -62,7 +62,7 @@ struct SourceControlNavigatorChangesList: View { selectedFiles.count == 1, let file = selection.first { DispatchQueue.main.async { - workspace.editorManager.openTab(item: file) + workspace.editorManager?.openTab(item: file) } } } @@ -72,7 +72,7 @@ struct SourceControlNavigatorChangesList: View { newSelection.count == 1, let file = newSelection.first { DispatchQueue.main.async { - workspace.editorManager.openTab(item: file) + workspace.editorManager?.openTab(item: file) } } } diff --git a/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift b/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift index 429d45552..883ac3a17 100644 --- a/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift +++ b/CodeEdit/Features/Settings/Pages/Keybindings/Models/KeybindingsSettings.swift @@ -28,16 +28,6 @@ extension SettingsData { forKey: .keybindings ) ?? .init() appendNew() - - let mgr = CommandManager.shared - let wrap = CommandClosureWrapper.init(closure: { - print("testing closure") - }) - mgr.addCommand( - name: "Send test to console", - title: "Send test to console", id: "codeedit.test", command: wrap - ) - mgr.executeCommand("test") } /// Adds new keybindings if they were added to default_keybindings.json. diff --git a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift index 019f1aeb2..2378099c8 100644 --- a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift +++ b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift @@ -109,7 +109,7 @@ extension SettingsData { name: "Toggle Type-Over Completion", title: "Toggle Type-Over Completion", id: "prefs.text_editing.type_over_completion", - command: CommandClosureWrapper { + command: { Settings.shared.preferences.textEditing.enableTypeOverCompletion.toggle() } ) @@ -118,7 +118,7 @@ extension SettingsData { name: "Toggle Autocomplete Braces", title: "Toggle Autocomplete Braces", id: "prefs.text_editing.autocomplete_braces", - command: CommandClosureWrapper { + command: { Settings.shared.preferences.textEditing.autocompleteBraces.toggle() } ) @@ -127,7 +127,7 @@ extension SettingsData { name: "Toggle Word Wrap", title: "Toggle Word Wrap", id: "prefs.text_editing.wrap_lines_to_editor_width", - command: CommandClosureWrapper { + command: { Settings[\.textEditing].wrapLinesToEditorWidth.toggle() } ) diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift index 16d525d81..1e5c2d15d 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift @@ -29,7 +29,7 @@ internal struct StatusBarToggleUtilityAreaButton: View { name: "Toggle Utility Area", title: "Toggle Utility Area", id: "open.drawer", - command: CommandClosureWrapper.init(closure: utilityAreaViewModel.togglePanel) + command: { [weak utilityAreaViewModel] in utilityAreaViewModel?.togglePanel() } ) } } @@ -38,7 +38,7 @@ internal struct StatusBarToggleUtilityAreaButton: View { name: "Toggle Utility Area", title: "Toggle Utility Area", id: "open.drawer", - command: CommandClosureWrapper.init(closure: utilityAreaViewModel.togglePanel) + command: { [weak utilityAreaViewModel] in utilityAreaViewModel?.togglePanel() } ) } } diff --git a/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift b/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift index 867c8d43f..63d29d92e 100644 --- a/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift +++ b/CodeEdit/Features/UtilityArea/TerminalUtility/UtilityAreaTerminalView.swift @@ -86,29 +86,6 @@ struct UtilityAreaTerminalView: View { return utilityAreaViewModel.terminals.first(where: { $0.id == id }) ?? nil } - private func updateTerminal(_ id: UUID, title: String? = nil) { - let terminalIndex = utilityAreaViewModel.terminals.firstIndex(where: { $0.id == id }) - if terminalIndex != nil { - updateTerminalByReference(of: &utilityAreaViewModel.terminals[terminalIndex!], title: title) - } - } - - func updateTerminalByReference( - of terminal: inout UtilityAreaTerminal, - title: String? = nil - ) { - if let newTitle = title { - if !terminal.customTitle { - terminal.title = newTitle - } - terminal.terminalTitle = newTitle - } - } - - func handleTitleChange(id: UUID, title: String) { - updateTerminal(id, title: title) - } - /// Returns the `background` color of the selected theme private var backgroundColor: NSColor { if let selectedTheme = matchAppearance && darkAppearance @@ -134,10 +111,11 @@ struct UtilityAreaTerminalView: View { TerminalEmulatorView( url: terminal.url!, shellType: terminal.shell, - onTitleChange: { newTitle in + onTitleChange: { [weak terminal] newTitle in + guard let id = terminal?.id else { return } // This can be called whenever, even in a view update so it needs to be dispatched. - DispatchQueue.main.async { - handleTitleChange(id: terminal.id, title: newTitle) + DispatchQueue.main.async { [weak utilityAreaViewModel] in + utilityAreaViewModel?.updateTerminal(id, title: newTitle) } } ) diff --git a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift b/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift index 671ccf327..b713ed70e 100644 --- a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift +++ b/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift @@ -56,4 +56,21 @@ class UtilityAreaViewModel: ObservableObject { self.isCollapsed.toggle() } } + + /// Update a terminal's title. + /// - Parameters: + /// - id: The id of the terminal to update. + /// - title: The title to set. If left `nil`, will set the terminal's + /// ``UtilityAreaTerminal/customTitle`` to `false`. + func updateTerminal(_ id: UUID, title: String?) { + guard let terminal = terminals.first(where: { $0.id == id }) else { return } + if let newTitle = title { + if !terminal.customTitle { + terminal.title = newTitle + } + terminal.terminalTitle = newTitle + } else { + terminal.customTitle = false + } + } } diff --git a/CodeEdit/Features/WindowCommands/FileCommands.swift b/CodeEdit/Features/WindowCommands/FileCommands.swift index 03b0c199e..1c1f3e5aa 100644 --- a/CodeEdit/Features/WindowCommands/FileCommands.swift +++ b/CodeEdit/Features/WindowCommands/FileCommands.swift @@ -11,7 +11,7 @@ struct FileCommands: Commands { @Environment(\.openWindow) private var openWindow - @State var windowController: CodeEditWindowController? + @UpdatingWindowController var windowController @FocusedObject var utilityAreaViewModel: UtilityAreaViewModel? @@ -44,7 +44,7 @@ struct FileCommands: Commands { if NSApp.target(forAction: #selector(CodeEditWindowController.closeCurrentTab(_:))) != nil { NSApp.sendAction(#selector(CodeEditWindowController.closeCurrentTab(_:)), to: nil, from: nil) } else { - NSApp.sendAction(#selector(NSWindow.close), to: nil, from: nil) + NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: keyWindow, from: nil) } } .keyboardShortcut("w") @@ -57,19 +57,19 @@ struct FileCommands: Commands { from: nil ) } else { - NSApp.sendAction(#selector(NSWindow.close), to: nil, from: nil) + NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: keyWindow, from: nil) } } .keyboardShortcut("w", modifiers: [.control, .shift, .command]) Button("Close Window") { - NSApp.sendAction(#selector(NSWindow.close), to: nil, from: nil) + NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: keyWindow, from: nil) } .keyboardShortcut("w", modifiers: [.shift, .command]) Button("Close Workspace") { guard let keyWindow = NSApplication.shared.keyWindow else { return } - NSApp.sendAction(#selector(NSWindow.close), to: keyWindow, from: nil) + NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: keyWindow, from: nil) } .keyboardShortcut("w", modifiers: [.control, .option, .command]) .disabled(!(NSApplication.shared.keyWindow?.windowController is CodeEditWindowController)) @@ -87,9 +87,6 @@ struct FileCommands: Commands { NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) } .disabled(windowController?.workspace == nil) - .onReceive(NSApp.publisher(for: \.keyWindow)) { window in - windowController = window?.windowController as? CodeEditWindowController - } Divider() diff --git a/CodeEdit/Features/WindowCommands/Utils/WindowControllerPropertyWrapper.swift b/CodeEdit/Features/WindowCommands/Utils/WindowControllerPropertyWrapper.swift new file mode 100644 index 000000000..f5130a034 --- /dev/null +++ b/CodeEdit/Features/WindowCommands/Utils/WindowControllerPropertyWrapper.swift @@ -0,0 +1,66 @@ +// +// WindowControllerPropertyWrapper.swift +// CodeEdit +// +// Created by Khan Winter on 7/14/24. +// + +import AppKit +import SwiftUI +import Combine + +/// Provides an auto-updating reference to ``CodeEditWindowController``. The value will update as the key window +/// changes, and does not keep a strong reference to the controller. +/// +/// Sample usage: +/// ```swift +/// struct WindowCommands: Commands { +/// @UpdatingWindowController var windowController +/// +/// var body: some Commands { +/// Button("Button that needs the window") { +/// print("Window exists") +/// } +/// .disabled(windowController == nil) +/// } +/// } +/// ``` +@propertyWrapper +struct UpdatingWindowController: DynamicProperty { + @StateObject var box = WindowControllerBox() + + var wrappedValue: CodeEditWindowController? { + box.controller + } + + class WindowControllerBox: ObservableObject { + public private(set) weak var controller: CodeEditWindowController? + + private var objectWillChangeCancellable: AnyCancellable? + private var utilityAreaCancellable: AnyCancellable? // ``ViewCommands`` needs this. + private var windowCancellable: AnyCancellable? + + init() { + windowCancellable = NSApp.publisher(for: \.keyWindow).sink { [weak self] window in + self?.setNewController(window?.windowController as? CodeEditWindowController) + } + } + + func setNewController(_ controller: CodeEditWindowController?) { + objectWillChangeCancellable?.cancel() + objectWillChangeCancellable = nil + utilityAreaCancellable?.cancel() + utilityAreaCancellable = nil + + self.controller = controller + + objectWillChangeCancellable = controller?.objectWillChange.sink { [weak self] in + self?.objectWillChange.send() + } + utilityAreaCancellable = controller?.workspace?.utilityAreaModel?.objectWillChange.sink { [weak self] in + self?.objectWillChange.send() + } + self.objectWillChange.send() + } + } +} diff --git a/CodeEdit/Features/WindowCommands/ViewCommands.swift b/CodeEdit/Features/WindowCommands/ViewCommands.swift index 1326a93dc..c11aff90d 100644 --- a/CodeEdit/Features/WindowCommands/ViewCommands.swift +++ b/CodeEdit/Features/WindowCommands/ViewCommands.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Combine struct ViewCommands: Commands { @AppSettings(\.textEditing.font.size) @@ -17,16 +18,14 @@ struct ViewCommands: Commands { @AppSettings(\.general.dimEditorsWithoutFocus) var dimEditorsWithoutFocus - @State private var windowController: CodeEditWindowController? - - private let documentController: CodeEditDocumentController = CodeEditDocumentController() - @FocusedBinding(\.navigationSplitViewVisibility) var navigationSplitViewVisibility @FocusedBinding(\.inspectorVisibility) var inspectorVisibility + @UpdatingWindowController var windowController: CodeEditWindowController? + var body: some Commands { CommandGroup(after: .toolbar) { Button("Show Command Palette") { @@ -72,17 +71,7 @@ struct ViewCommands: Commands { Divider() - HideCommands( - windowController: windowController ?? CodeEditWindowController( - window: nil, - workspace: nil, - taskNotificationHandler: TaskNotificationHandler() - ), - utilityAreaModel: windowController?.workspace?.utilityAreaModel ?? UtilityAreaViewModel() - ) - .onReceive(NSApp.publisher(for: \.keyWindow)) { window in - windowController = window?.windowController as? CodeEditWindowController - } + HideCommands() Divider() @@ -102,50 +91,51 @@ struct ViewCommands: Commands { } } -struct HideCommands: View { - @ObservedObject var windowController: CodeEditWindowController - @ObservedObject var utilityAreaModel: UtilityAreaViewModel - - var navigatorCollapsed: Bool { - windowController.navigatorCollapsed - } - - var inspectorCollapsed: Bool { - windowController.inspectorCollapsed - } - - var utilityAreaCollapsed: Bool { - utilityAreaModel.isCollapsed - } +extension ViewCommands { + struct HideCommands: View { + @UpdatingWindowController var windowController: CodeEditWindowController? - var toolbarCollapsed: Bool { - windowController.toolbarCollapsed - } + var navigatorCollapsed: Bool { + windowController?.navigatorCollapsed ?? true + } - var body: some View { - Button("\(navigatorCollapsed ? "Show" : "Hide") Navigator") { - windowController.toggleFirstPanel() + var inspectorCollapsed: Bool { + windowController?.inspectorCollapsed ?? true } - .disabled(windowController.window == nil) - .keyboardShortcut("0", modifiers: [.command]) - Button("\(inspectorCollapsed ? "Show" : "Hide") Inspector") { - windowController.toggleLastPanel() + var utilityAreaCollapsed: Bool { + windowController?.workspace?.utilityAreaModel?.isCollapsed ?? true } - .disabled(windowController.window == nil) - .keyboardShortcut("i", modifiers: [.control, .command]) - Button("\(utilityAreaCollapsed ? "Show" : "Hide") Utility Area") { - CommandManager.shared.executeCommand("open.drawer") + var toolbarCollapsed: Bool { + windowController?.toolbarCollapsed ?? true } - .disabled(windowController.window == nil) - .keyboardShortcut("y", modifiers: [.shift, .command]) - Button("\(toolbarCollapsed ? "Show" : "Hide") Toolbar") { - windowController.toggleToolbar() + var body: some View { + Button("\(navigatorCollapsed ? "Show" : "Hide") Navigator") { + windowController?.toggleFirstPanel() + } + .disabled(windowController == nil) + .keyboardShortcut("0", modifiers: [.command]) + + Button("\(inspectorCollapsed ? "Show" : "Hide") Inspector") { + windowController?.toggleLastPanel() + } + .disabled(windowController == nil) + .keyboardShortcut("i", modifiers: [.control, .command]) + + Button("\(utilityAreaCollapsed ? "Show" : "Hide") Utility Area") { + CommandManager.shared.executeCommand("open.drawer") + } + .disabled(windowController == nil) + .keyboardShortcut("y", modifiers: [.shift, .command]) + + Button("\(toolbarCollapsed ? "Show" : "Hide") Toolbar") { + windowController?.toggleToolbar() + } + .disabled(windowController == nil) + .keyboardShortcut("t", modifiers: [.option, .command]) } - .disabled(windowController.window == nil) - .keyboardShortcut("t", modifiers: [.option, .command]) } } diff --git a/CodeEdit/Utils/Environment/Env+Window.swift b/CodeEdit/Utils/Environment/Env+Window.swift index 5a18ba4d4..15b0c414c 100644 --- a/CodeEdit/Utils/Environment/Env+Window.swift +++ b/CodeEdit/Utils/Environment/Env+Window.swift @@ -7,12 +7,17 @@ import SwiftUI +struct WindowBox { + weak var value: NSWindow? +} + struct NSWindowEnvironmentKey: EnvironmentKey { - static var defaultValue = NSWindow() + typealias Value = WindowBox + static var defaultValue = WindowBox(value: nil) } extension EnvironmentValues { - var window: NSWindowEnvironmentKey.Value { + var window: WindowBox { get { self[NSWindowEnvironmentKey.self] } set { self[NSWindowEnvironmentKey.self] = newValue } } diff --git a/CodeEdit/Features/Documents/String+AppearancesOfSubstring.swift b/CodeEdit/Utils/Extensions/String/String+AppearancesOfSubstring.swift similarity index 100% rename from CodeEdit/Features/Documents/String+AppearancesOfSubstring.swift rename to CodeEdit/Utils/Extensions/String/String+AppearancesOfSubstring.swift diff --git a/CodeEdit/Features/Documents/String+Character.swift b/CodeEdit/Utils/Extensions/String/String+Character.swift similarity index 100% rename from CodeEdit/Features/Documents/String+Character.swift rename to CodeEdit/Utils/Extensions/String/String+Character.swift diff --git a/CodeEdit/WindowObserver.swift b/CodeEdit/WindowObserver.swift index 17978b943..530505f4c 100644 --- a/CodeEdit/WindowObserver.swift +++ b/CodeEdit/WindowObserver.swift @@ -9,7 +9,7 @@ import SwiftUI struct WindowObserver: View { - var window: NSWindow + var window: WindowBox @ViewBuilder var content: Content diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index b26d9f4c3..a1323fd49 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -8,8 +8,8 @@ import SwiftUI struct WorkspaceView: View { - @Environment(\.window) - private var window: NSWindow + @Environment(\.window.value) + private var window: NSWindow? @Environment(\.colorScheme) private var colorScheme diff --git a/CodeEditTests/Features/Documents/DocumentsUnitTests.swift b/CodeEditTests/Features/Documents/DocumentsUnitTests.swift index c116d7a19..5cdefd143 100644 --- a/CodeEditTests/Features/Documents/DocumentsUnitTests.swift +++ b/CodeEditTests/Features/Documents/DocumentsUnitTests.swift @@ -14,6 +14,7 @@ final class DocumentsUnitTests: XCTestCase { private var hapticFeedbackPerformerMock: NSHapticFeedbackPerformerMock! private var navigatorViewModel: NavigatorSidebarViewModel! private var window: NSWindow! + private var workspace = WorkspaceDocument() // MARK: - Lifecycle @@ -23,7 +24,7 @@ final class DocumentsUnitTests: XCTestCase { navigatorViewModel = .init() window = NSWindow() splitViewController = .init( - workspace: WorkspaceDocument(), + workspace: workspace, navigatorViewModel: navigatorViewModel, windowRef: window, hapticPerformer: hapticFeedbackPerformerMock diff --git a/CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+FindAndReplaceTests.swift b/CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+FindAndReplaceTests.swift index 9e92c5a5a..d358b0991 100644 --- a/CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+FindAndReplaceTests.swift +++ b/CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+FindAndReplaceTests.swift @@ -8,12 +8,16 @@ import XCTest @testable import CodeEdit +// swiftlint:disable:next type_body_length final class FindAndReplaceTests: XCTestCase { private var directory: URL! private var files: [CEWorkspaceFile] = [] private var mockWorkspace: WorkspaceDocument! private var searchState: WorkspaceDocument.SearchState! + private var folder1File: CEWorkspaceFile? + private var folder2File: CEWorkspaceFile? + // MARK: - Setup /// A mock WorkspaceDocument is created /// 3 mock files are added to the index @@ -35,7 +39,9 @@ final class FindAndReplaceTests: XCTestCase { // Add a few files let folder1 = directory.appending(path: "Folder 2") + folder1File = CEWorkspaceFile(url: folder1) let folder2 = directory.appending(path: "Longer Folder With Some 💯 Special Chars ⁉️") + folder2File = CEWorkspaceFile(url: folder2) try FileManager.default.createDirectory(at: folder1, withIntermediateDirectories: true) try FileManager.default.createDirectory(at: folder2, withIntermediateDirectories: true) @@ -55,8 +61,8 @@ final class FindAndReplaceTests: XCTestCase { files = fileURLs.map { CEWorkspaceFile(url: $0) } - files[1].parent = CEWorkspaceFile(url: folder1) - files[2].parent = CEWorkspaceFile(url: folder2) + files[1].parent = folder1File + files[2].parent = folder2File await mockWorkspace.searchState?.addProjectToIndex() diff --git a/CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+IndexTests.swift b/CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+IndexTests.swift index edc308e93..a0f161133 100644 --- a/CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+IndexTests.swift +++ b/CodeEditTests/Features/Documents/WorkspaceDocument+SearchState+IndexTests.swift @@ -14,6 +14,9 @@ final class WorkspaceDocumentIndexTests: XCTestCase { private var mockWorkspace: WorkspaceDocument! private var searchState: WorkspaceDocument.SearchState! + private var folder1File: CEWorkspaceFile? + private var folder2File: CEWorkspaceFile? + // MARK: - Setup /// A mock WorkspaceDocument is created /// 3 mock files are added to the index @@ -35,7 +38,9 @@ final class WorkspaceDocumentIndexTests: XCTestCase { // Add a few files let folder1 = directory.appending(path: "Folder 2") + folder1File = CEWorkspaceFile(url: folder1) let folder2 = directory.appending(path: "Longer Folder With Some 💯 Special Chars ⁉️") + folder2File = CEWorkspaceFile(url: folder2) try FileManager.default.createDirectory(at: folder1, withIntermediateDirectories: true) try FileManager.default.createDirectory(at: folder2, withIntermediateDirectories: true) @@ -55,8 +60,8 @@ final class WorkspaceDocumentIndexTests: XCTestCase { files = fileURLs.map { CEWorkspaceFile(url: $0) } - files[1].parent = CEWorkspaceFile(url: folder1) - files[2].parent = CEWorkspaceFile(url: folder2) + files[1].parent = folder1File + files[2].parent = folder2File await mockWorkspace.searchState?.addProjectToIndex()