From ed13435dc17f2de30f48fe9751d2594c35d01570 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 20 Feb 2023 13:37:51 +0100 Subject: [PATCH 01/82] Semi working version of splitview Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 63 +++++++++++- .../xcshareddata/swiftpm/Package.resolved | 9 ++ .../Views/WorkspaceCodeFileView.swift | 53 ++++------ .../Documents/WorkspaceDocument.swift | 36 ++++--- .../ExtensionNavigatorItemView.swift | 2 +- CodeEdit/Features/SplitView/EditorView.swift | 47 +++++++++ .../SplitView/Environment+SplitEditor.swift | 19 ++++ .../SplitView/WorkspaceTabGroupView.swift | 55 +++++++++++ .../Models/TabBarItemID.swift | 0 .../Models/TabBarItemRepresentable.swift | 0 .../Features/Tabs/TabGroup/TabGroup.swift | 25 +++++ .../Features/Tabs/TabGroup/TabGroupData.swift | 19 ++++ .../TabGroup/WorkspaceSplitViewData.swift | 42 ++++++++ .../Views/TabBarAccessory.swift | 0 .../Views/TabBarContextMenu.swift | 29 +++++- .../Views/TabBarDivider.swift | 0 .../Views/TabBarItemBackground.swift | 0 .../Views/TabBarItemButtonStyle.swift | 0 .../Views/TabBarItemCloseButton.swift | 0 .../Views/TabBarItemView.swift | 97 +++++++++---------- .../{TabBar => Tabs}/Views/TabBarNative.swift | 0 .../{TabBar => Tabs}/Views/TabBarView.swift | 76 ++++++++------- .../{TabBar => Tabs}/Views/TabBarXcode.swift | 0 CodeEdit/WorkspaceView.swift | 38 ++------ 24 files changed, 441 insertions(+), 169 deletions(-) create mode 100644 CodeEdit/Features/SplitView/EditorView.swift create mode 100644 CodeEdit/Features/SplitView/Environment+SplitEditor.swift create mode 100644 CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift rename CodeEdit/Features/{TabBar => Tabs}/Models/TabBarItemID.swift (100%) rename CodeEdit/Features/{TabBar => Tabs}/Models/TabBarItemRepresentable.swift (100%) create mode 100644 CodeEdit/Features/Tabs/TabGroup/TabGroup.swift create mode 100644 CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift create mode 100644 CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarAccessory.swift (100%) rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarContextMenu.swift (81%) rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarDivider.swift (100%) rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarItemBackground.swift (100%) rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarItemButtonStyle.swift (100%) rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarItemCloseButton.swift (100%) rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarItemView.swift (83%) rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarNative.swift (100%) rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarView.swift (90%) rename CodeEdit/Features/{TabBar => Tabs}/Views/TabBarXcode.swift (100%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 69ed9bfcea..afcdaeb3ef 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -313,6 +313,13 @@ 58FD7609291EA1CB0051D6E4 /* CommandPaletteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD7607291EA1CB0051D6E4 /* CommandPaletteView.swift */; }; 5C4BB1E128212B1E00A92FB2 /* World.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4BB1E028212B1E00A92FB2 /* World.swift */; }; 6C05A8AF284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */; }; + 6C147C4029A328BC0089B630 /* WorkspaceSplitViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3F29A328560089B630 /* WorkspaceSplitViewData.swift */; }; + 6C147C4129A328BF0089B630 /* TabGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3E29A3281D0089B630 /* TabGroup.swift */; }; + 6C147C4229A328C10089B630 /* TabGroupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3D29A3281D0089B630 /* TabGroupData.swift */; }; + 6C147C4529A329350089B630 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 6C147C4429A329350089B630 /* OrderedCollections */; }; + 6C147C4929A32A080089B630 /* EditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C4829A32A080089B630 /* EditorView.swift */; }; + 6C147C4B29A32A7B0089B630 /* Environment+SplitEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */; }; + 6C147C4D29A32AA30089B630 /* WorkspaceTabGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */; }; 6C14CEB028777D3C001468FE /* FindNavigatorListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */; }; 6C14CEB32877A68F001468FE /* FindNavigatorMatchListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */; }; 6C4104E3297C87A000F472BA /* BlurButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E2297C87A000F472BA /* BlurButtonStyle.swift */; }; @@ -704,6 +711,12 @@ 58FD7607291EA1CB0051D6E4 /* CommandPaletteView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandPaletteView.swift; sourceTree = ""; }; 5C4BB1E028212B1E00A92FB2 /* World.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = World.swift; sourceTree = ""; }; 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+Listeners.swift"; sourceTree = ""; }; + 6C147C3D29A3281D0089B630 /* TabGroupData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabGroupData.swift; sourceTree = ""; }; + 6C147C3E29A3281D0089B630 /* TabGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabGroup.swift; sourceTree = ""; }; + 6C147C3F29A328560089B630 /* WorkspaceSplitViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSplitViewData.swift; sourceTree = ""; }; + 6C147C4829A32A080089B630 /* EditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorView.swift; sourceTree = ""; }; + 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+SplitEditor.swift"; sourceTree = ""; }; + 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceTabGroupView.swift; sourceTree = ""; }; 6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorListViewController.swift; sourceTree = ""; }; 6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorMatchListCell.swift; sourceTree = ""; }; 6C4104E2297C87A000F472BA /* BlurButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurButtonStyle.swift; sourceTree = ""; }; @@ -762,6 +775,7 @@ 58F2EB17292FB74D004A9BDE /* CodeEditTextView in Frameworks */, 5879826F292EC9870085B254 /* GRDB in Frameworks */, 58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */, + 6C147C4529A329350089B630 /* OrderedCollections in Frameworks */, 5879826B292EC7B00085B254 /* Light-Swift-Untar in Frameworks */, 5879828A292ED15F0085B254 /* SwiftTerm in Frameworks */, 2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */, @@ -989,13 +1003,14 @@ path = NavigatorSidebar; sourceTree = ""; }; - 287776EB27E350BA00D46668 /* TabBar */ = { + 287776EB27E350BA00D46668 /* Tabs */ = { isa = PBXGroup; children = ( + 6C147C3C29A328020089B630 /* TabGroup */, 58AFAA272933C65C00482B53 /* Models */, 58AFAA262933C65000482B53 /* Views */, ); - path = TabBar; + path = Tabs; sourceTree = ""; }; 2BE487ED28245162003F3F64 /* OpenWithCodeEdit */ = { @@ -1088,8 +1103,9 @@ 287776EA27E350A100D46668 /* NavigatorSidebar */, 5878DAA0291AE76700DD95A3 /* QuickOpen */, 58798210292D92370085B254 /* Search */, + 6C147C4729A329E50089B630 /* SplitView */, 588224FF292C280D00E83CDE /* StatusBar */, - 287776EB27E350BA00D46668 /* TabBar */, + 287776EB27E350BA00D46668 /* Tabs */, 5879827E292ED0FB0085B254 /* TerminalEmulator */, 581BFB4B2926431000D251EC /* Welcome */, ); @@ -2206,6 +2222,26 @@ name = Frameworks; sourceTree = ""; }; + 6C147C3C29A328020089B630 /* TabGroup */ = { + isa = PBXGroup; + children = ( + 6C147C3F29A328560089B630 /* WorkspaceSplitViewData.swift */, + 6C147C3E29A3281D0089B630 /* TabGroup.swift */, + 6C147C3D29A3281D0089B630 /* TabGroupData.swift */, + ); + path = TabGroup; + sourceTree = ""; + }; + 6C147C4729A329E50089B630 /* SplitView */ = { + isa = PBXGroup; + children = ( + 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */, + 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */, + 6C147C4829A32A080089B630 /* EditorView.swift */, + ); + path = SplitView; + sourceTree = ""; + }; 6C14CEB12877A5BE001468FE /* FindNavigatorResultList */ = { isa = PBXGroup; children = ( @@ -2370,6 +2406,7 @@ 58F2EB16292FB74D004A9BDE /* CodeEditTextView */, 58F2EB19292FB91C004A9BDE /* Preferences */, 58F2EB1D292FB954004A9BDE /* Sparkle */, + 6C147C4429A329350089B630 /* OrderedCollections */, ); productName = CodeEdit; productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */; @@ -2463,6 +2500,7 @@ 58F2EB18292FB91C004A9BDE /* XCRemoteSwiftPackageReference "Preferences" */, 58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference "Sparkle" */, 583E529A29361BAB001AB554 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, + 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */, ); productRefGroup = B658FB2D27DA9E0F00EA4DBD /* Products */; projectDirPath = ""; @@ -2709,6 +2747,7 @@ 587B9E8729301D8F00AC7927 /* GitHubRepositories.swift in Sources */, 587B9DA329300ABD00AC7927 /* SettingsTextEditor.swift in Sources */, 2072FA1A280D872600C7F8D4 /* LineEndings.swift in Sources */, + 6C147C4D29A32AA30089B630 /* WorkspaceTabGroupView.swift in Sources */, 58F2EAFB292FB2B0004A9BDE /* GitLabLoginView.swift in Sources */, 587B9E7B29301D8F00AC7927 /* GitHubRouter.swift in Sources */, 201169E22837B3D800F92B46 /* SourceControlNavigatorChangesView.swift in Sources */, @@ -2777,6 +2816,7 @@ 58822531292C280D00E83CDE /* View+isHovering.swift in Sources */, 587B9E9929301D8F00AC7927 /* GitChangedFile.swift in Sources */, 58F2EAF2292FB2B0004A9BDE /* EditorThemeView.swift in Sources */, + 6C147C4B29A32A7B0089B630 /* Environment+SplitEditor.swift in Sources */, 2897E1C72979A29200741E32 /* OffsettableScrollView.swift in Sources */, 58F2EB0E292FB2B0004A9BDE /* SoftwareUpdater.swift in Sources */, 587B9E9529301D8F00AC7927 /* BitBucketUser.swift in Sources */, @@ -2819,6 +2859,8 @@ 04C3254F2800AA4700C8DA2D /* ExtensionInstallationView.swift in Sources */, 58822530292C280D00E83CDE /* FilterTextField.swift in Sources */, 58798266292EC4080085B254 /* APIResponse.swift in Sources */, + 6C147C4929A32A080089B630 /* EditorView.swift in Sources */, + 6C147C4129A328BF0089B630 /* TabGroup.swift in Sources */, B6D7EA592971078500301FAC /* InspectorSection.swift in Sources */, 58F2EAEF292FB2B0004A9BDE /* ThemePreferencesView.swift in Sources */, B6EE989027E8879A00CDD8AB /* InspectorSidebarView.swift in Sources */, @@ -2857,10 +2899,12 @@ 28B0A19827E385C300B73177 /* NavigatorSidebarToolbarTop.swift in Sources */, 587B9E8629301D8F00AC7927 /* GitHubComment.swift in Sources */, 58F2EAE9292FB2B0004A9BDE /* SourceControlPreferencesView.swift in Sources */, + 6C147C4029A328BC0089B630 /* WorkspaceSplitViewData.swift in Sources */, 587B9E9029301D8F00AC7927 /* BitBucketTokenRouter.swift in Sources */, B6C6A42E29771A8D00A3D28F /* TabBarItemButtonStyle.swift in Sources */, 58822525292C280D00E83CDE /* StatusBarMenuLabel.swift in Sources */, 58F2EB11292FB2B0004A9BDE /* PreferencesToolbar.swift in Sources */, + 6C147C4229A328C10089B630 /* TabGroupData.swift in Sources */, 58F2EAF0292FB2B0004A9BDE /* PreviewThemeView.swift in Sources */, 58F2EAFF292FB2B0004A9BDE /* KeybindingsPreferencesView.swift in Sources */, 6CDA84AD284C1BA000C1CC3A /* TabBarContextMenu.swift in Sources */, @@ -3829,6 +3873,14 @@ version = 2.3.0; }; }; + 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-collections.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3882,6 +3934,11 @@ package = 58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference "Sparkle" */; productName = Sparkle; }; + 6C147C4429A329350089B630 /* OrderedCollections */ = { + isa = XCSwiftPackageProductDependency; + package = 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */; + productName = OrderedCollections; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B658FB2427DA9E0F00EA4DBD /* Project object */; diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0c0ad57b7f..c1ede28905 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -90,6 +90,15 @@ "version" : "0.4.2" } }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", + "version" : "1.0.4" + } + }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", diff --git a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift b/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift index 01b3b1e7ac..de56d0a772 100644 --- a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift +++ b/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift @@ -12,45 +12,34 @@ struct WorkspaceCodeFileView: View { @EnvironmentObject private var workspace: WorkspaceDocument + var file: WorkspaceClient.FileItem + + var document: CodeFileDocument? { + workspace.selectionState.openedCodeFiles[file] + } + @StateObject private var prefs: AppPreferencesModel = .shared @ViewBuilder var codeView: some View { - ZStack { - if let item = workspace.selectionState.openFileItems.first(where: { file in - if file.tabID == workspace.selectionState.selectedId { - print("Item loaded is: ", file.url) + if let document { + Group { + switch document.typeOfFile { + case .some(.text), .some(.data): + CodeFileView(codeFile: document) + default: + otherFileView(document, for: file) } - return file.tabID == workspace.selectionState.selectedId - }) { - if let fileItem = workspace.selectionState.openedCodeFiles[item] { - if fileItem.typeOfFile == .text || fileItem.typeOfFile == .data { - codeFileView(fileItem, for: item) - } else { - otherFileView(fileItem, for: item) - } - } - } else { - Text("No Editor") - .font(.system(size: 17)) - .foregroundColor(.secondary) - .frame(minHeight: 0) - .clipped() } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - - @ViewBuilder - private func codeFileView( - _ codeFile: CodeFileDocument, - for item: WorkspaceClient.FileItem - ) -> some View { - VStack(spacing: 0) { - BreadcrumbsView(file: item, tappedOpenFile: workspace.openTab(item:)) - Divider() - CodeFileView(codeFile: codeFile) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else { + Spacer() + VStack(spacing: 10) { + ProgressView() + Text("Opening \(file.fileName)...") + } + Spacer() } } diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 3713bd64bc..9fbb4bbf3b 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -22,6 +22,16 @@ import CodeEditKit @Published var selectionState: WorkspaceSelectionState = .init() @Published var fileItems: [WorkspaceClient.FileItem] = [] + @Published var tabs: TabGroup + + var activeTab: TabGroupData + + override init() { + self.activeTab = .init() + self.tabs = .horizontal(.init(.horizontal, tabgroups: [.one(activeTab)])) + super.init() + } + var statusBarModel: StatusBarViewModel? var searchState: SearchState? var quickOpenViewModel: QuickOpenViewModel? @@ -39,23 +49,17 @@ import CodeEditKit // MARK: Open Tabs /// Opens new tab /// - Parameter item: any item which can be represented as a tab - func openTab(item: TabBarItemRepresentable) { - do { - updateNewlyOpenedTabs(item: item) - if selectionState.selectedId != item.tabID { - selectionState.selectedId = item.tabID - } - switch item.tabID { - case .codeEditor: - guard let file = item as? WorkspaceClient.FileItem else { return } - try self.openFile(item: file) - case .extensionInstallation: - guard let plugin = item as? Plugin else { return } - self.openExtension(item: plugin) + func openTab(item: WorkspaceClient.FileItem) { + Task { + await MainActor.run { + activeTab.files.append(item) + activeTab.selected = item + do { + try openFile(item: item) + } catch { + Swift.print(error) + } } - - } catch let err { - Swift.print(err) } } diff --git a/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorItemView.swift b/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorItemView.swift index 3a4024e2a3..1a52cff88b 100644 --- a/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorItemView.swift +++ b/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorItemView.swift @@ -13,7 +13,7 @@ struct ExtensionNavigatorItemView: View { var body: some View { Button { - document.openTab(item: plugin) +// document.openTab(item: plugin) } label: { ZStack { HStack { diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift new file mode 100644 index 0000000000..0bff836de2 --- /dev/null +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -0,0 +1,47 @@ +// +// EditorView.swift +// CodeEdit +// +// Created by Wouter Hennen on 20/02/2023. +// + +import SwiftUI + +struct EditorView: View { + var tabgroup: TabGroup + + var body: some View { + switch tabgroup { + case .one(let detailTabGroup): + WorkspaceTabGroupView(tabgroup: detailTabGroup) + case .vertical(let data), .horizontal(let data): + SubEditorView(data: data) + } + } + + struct SubEditorView: View { + @ObservedObject var data: WorkspaceSplitViewData + + var body: some View { + switch data.axis { + case .vertical: + VSplitView { + splitView + } + case .horizontal: + HSplitView { + splitView + } + } + } + + var splitView: some View { + ForEach(Array(data.tabgroups.enumerated()), id: \.offset) { index, item in + EditorView(tabgroup: item) + .environment(\.splitEditor) { edge, newTabGroup in + data.split(edge, at: index, new: newTabGroup) + } + } + } + } +} diff --git a/CodeEdit/Features/SplitView/Environment+SplitEditor.swift b/CodeEdit/Features/SplitView/Environment+SplitEditor.swift new file mode 100644 index 0000000000..a96c0fab56 --- /dev/null +++ b/CodeEdit/Features/SplitView/Environment+SplitEditor.swift @@ -0,0 +1,19 @@ +// +// Environment+SplitEditor.swift +// CodeEdit +// +// Created by Wouter Hennen on 16/02/2023. +// + +import SwiftUI + +struct SplitEditorEnvironmentKey: EnvironmentKey { + static var defaultValue: (Edge, TabGroupData) -> Void = { _, _ in } +} + +extension EnvironmentValues { + var splitEditor: SplitEditorEnvironmentKey.Value { + get { self[SplitEditorEnvironmentKey.self] } + set { self[SplitEditorEnvironmentKey.self] = newValue } + } +} diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift new file mode 100644 index 0000000000..bcfe4a1811 --- /dev/null +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -0,0 +1,55 @@ +// +// WorkspaceTabGroupView.swift +// CodeEdit +// +// Created by Wouter Hennen on 16/02/2023. +// + +import SwiftUI + +struct WorkspaceTabGroupView: View { + @ObservedObject var tabgroup: TabGroupData + + var body: some View { + VStack { + if let selected = tabgroup.selected { + WorkspaceCodeFileView(file: selected) + } else { + VStack { + Spacer() + Text("No Editor") + .font(.system(size: 17)) + .foregroundColor(.secondary) + .frame(minHeight: 0) + .clipped() + Spacer() + } + } + } + .frame(maxWidth: .infinity) + .ignoresSafeArea(.all) + .safeAreaInset(edge: .top, spacing: 0) { + VStack(spacing: 0) { + TabBarView() + .environmentObject(tabgroup) + + Divider() + if let file = tabgroup.selected { + BreadcrumbsView(file: file) { newFile in + print("Opening \(newFile.fileName)") + let index = tabgroup.files.firstIndex(of: file) + if let index { + tabgroup.files.insert(file, at: index) +// DispatchQueue.main.async { + tabgroup.files.remove(file) +// } + tabgroup.selected = file + } + } + Divider() + } + } + .background(EffectView(.titlebar, blendingMode: .withinWindow, emphasized: false)) + } + } +} diff --git a/CodeEdit/Features/TabBar/Models/TabBarItemID.swift b/CodeEdit/Features/Tabs/Models/TabBarItemID.swift similarity index 100% rename from CodeEdit/Features/TabBar/Models/TabBarItemID.swift rename to CodeEdit/Features/Tabs/Models/TabBarItemID.swift diff --git a/CodeEdit/Features/TabBar/Models/TabBarItemRepresentable.swift b/CodeEdit/Features/Tabs/Models/TabBarItemRepresentable.swift similarity index 100% rename from CodeEdit/Features/TabBar/Models/TabBarItemRepresentable.swift rename to CodeEdit/Features/Tabs/Models/TabBarItemRepresentable.swift diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift new file mode 100644 index 0000000000..2733f436a0 --- /dev/null +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift @@ -0,0 +1,25 @@ +// +// TabGroup.swift +// CodeEdit +// +// Created by Wouter Hennen on 06/02/2023. +// + +import Foundation + +enum TabGroup { + case one(TabGroupData) + case vertical(WorkspaceSplitViewData) + case horizontal(WorkspaceSplitViewData) + + func closeAllTabs(of file: WorkspaceClient.FileItem) { + switch self { + case .one(let tabGroupData): + tabGroupData.files.remove(file) + case .vertical(let data), .horizontal(let data): + data.tabgroups.forEach { + $0.closeAllTabs(of: file) + } + } + } +} diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift new file mode 100644 index 0000000000..1ad2dccf00 --- /dev/null +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -0,0 +1,19 @@ +// +// TabGroupData.swift +// CodeEdit +// +// Created by Wouter Hennen on 16/02/2023. +// + +import Foundation +import OrderedCollections + +final class TabGroupData: ObservableObject { + @Published var files: OrderedSet = [] + @Published var selected: WorkspaceClient.FileItem? + + init(files: OrderedSet = [], selected: WorkspaceClient.FileItem? = nil) { + self.files = files + self.selected = selected + } +} diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift new file mode 100644 index 0000000000..f92f92d4c9 --- /dev/null +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift @@ -0,0 +1,42 @@ +// +// WorkspaceSplitViewData.swift +// CodeEdit +// +// Created by Wouter Hennen on 16/02/2023. +// + +import SwiftUI + +class WorkspaceSplitViewData: ObservableObject { + @Published var tabgroups: [TabGroup] + + var axis: Axis + + init(_ axis: Axis, tabgroups: [TabGroup] = []) { + self.tabgroups = tabgroups + self.axis = axis + } + + // Splits the editor at a certain index into two separate editors. + func split(_ direction: Edge, at index: Int, new tabgroup: TabGroupData) { + switch (axis, direction) { + case (.horizontal, .trailing), (.vertical, .bottom): + tabgroups.insert(.one(tabgroup), at: index+1) + + case (.horizontal, .leading), (.vertical, .top): + tabgroups.insert(.one(tabgroup), at: index) + + case (.horizontal, .top): + tabgroups[index] = .vertical(.init(.vertical, tabgroups: [.one(tabgroup), tabgroups[index]])) + + case (.horizontal, .bottom): + tabgroups[index] = .vertical(.init(.vertical, tabgroups: [tabgroups[index], .one(tabgroup)])) + + case (.vertical, .leading): + tabgroups[index] = .horizontal(.init(.horizontal, tabgroups: [.one(tabgroup), tabgroups[index]])) + + case (.vertical, .trailing): + tabgroups[index] = .horizontal(.init(.horizontal, tabgroups: [tabgroups[index], .one(tabgroup)])) + } + } +} diff --git a/CodeEdit/Features/TabBar/Views/TabBarAccessory.swift b/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift similarity index 100% rename from CodeEdit/Features/TabBar/Views/TabBarAccessory.swift rename to CodeEdit/Features/Tabs/Views/TabBarAccessory.swift diff --git a/CodeEdit/Features/TabBar/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift similarity index 81% rename from CodeEdit/Features/TabBar/Views/TabBarContextMenu.swift rename to CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index 7a3adc59ba..d4015a1647 100644 --- a/CodeEdit/Features/TabBar/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -9,14 +9,14 @@ import Foundation import SwiftUI extension View { - func tabBarContextMenu(item: TabBarItemRepresentable, isTemporary: Bool) -> some View { + func tabBarContextMenu(item: WorkspaceClient.FileItem, isTemporary: Bool) -> some View { modifier(TabBarContextMenu(item: item, isTemporary: isTemporary)) } } struct TabBarContextMenu: ViewModifier { init( - item: TabBarItemRepresentable, + item: WorkspaceClient.FileItem, isTemporary: Bool ) { self.item = item @@ -26,13 +26,30 @@ struct TabBarContextMenu: ViewModifier { @EnvironmentObject var workspace: WorkspaceDocument - private var item: TabBarItemRepresentable + @EnvironmentObject + var tabs: TabGroupData + + @Environment(\.splitEditor) var splitEditor + + private var item: WorkspaceClient.FileItem private var isTemporary: Bool // swiftlint:disable:next function_body_length func body(content: Content) -> some View { content.contextMenu(menuItems: { Group { + Button("Split and open on the right") { + moveToNewSplit(.trailing) + } + Button("Split and open on the bottom") { + moveToNewSplit(.bottom) + } + Button("Split and open on the top") { + moveToNewSplit(.top) + } + Button("Split and open on the left") { + moveToNewSplit(.leading) + } Button("Close Tab") { withAnimation { workspace.closeTab(item: item.tabID) @@ -108,6 +125,12 @@ struct TabBarContextMenu: ViewModifier { NSPasteboard.general.setString(item.url.standardizedFileURL.path, forType: .string) } + func moveToNewSplit(_ edge: Edge) { + let newTabGroup = TabGroupData(files: [item], selected: item) + splitEditor(edge, newTabGroup) + tabs.files.remove(item) + } + /// Copies the relative path from the workspace folder to the given file item to the pasteboard. /// - Parameter item: The `FileItem` to use. private func copyRelativePath(item: WorkspaceClient.FileItem) { diff --git a/CodeEdit/Features/TabBar/Views/TabBarDivider.swift b/CodeEdit/Features/Tabs/Views/TabBarDivider.swift similarity index 100% rename from CodeEdit/Features/TabBar/Views/TabBarDivider.swift rename to CodeEdit/Features/Tabs/Views/TabBarDivider.swift diff --git a/CodeEdit/Features/TabBar/Views/TabBarItemBackground.swift b/CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift similarity index 100% rename from CodeEdit/Features/TabBar/Views/TabBarItemBackground.swift rename to CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift diff --git a/CodeEdit/Features/TabBar/Views/TabBarItemButtonStyle.swift b/CodeEdit/Features/Tabs/Views/TabBarItemButtonStyle.swift similarity index 100% rename from CodeEdit/Features/TabBar/Views/TabBarItemButtonStyle.swift rename to CodeEdit/Features/Tabs/Views/TabBarItemButtonStyle.swift diff --git a/CodeEdit/Features/TabBar/Views/TabBarItemCloseButton.swift b/CodeEdit/Features/Tabs/Views/TabBarItemCloseButton.swift similarity index 100% rename from CodeEdit/Features/TabBar/Views/TabBarItemCloseButton.swift rename to CodeEdit/Features/Tabs/Views/TabBarItemCloseButton.swift diff --git a/CodeEdit/Features/TabBar/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift similarity index 83% rename from CodeEdit/Features/TabBar/Views/TabBarItemView.swift rename to CodeEdit/Features/Tabs/Views/TabBarItemView.swift index a228b4e6d1..87170b92cf 100644 --- a/CodeEdit/Features/TabBar/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -8,6 +8,9 @@ import SwiftUI struct TabBarItemView: View { + + typealias Item = WorkspaceClient.FileItem + @Environment(\.colorScheme) private var colorScheme @@ -17,6 +20,8 @@ struct TabBarItemView: View { @Environment(\.isFullscreen) private var isFullscreen + @EnvironmentObject var workspace: WorkspaceDocument + /// User preferences. @StateObject private var prefs: AppPreferencesModel = .shared @@ -44,44 +49,42 @@ struct TabBarItemView: View { private var isAppeared: Bool = false /// The expected tab width in native tab bar style. - @Binding private var expectedWidth: CGFloat /// The id associating with the tab that is currently being dragged. /// /// When `nil`, then there is no tab being dragged. - @Binding - private var draggingTabId: TabBarItemID? + private var draggingTabId: Item.ID? - @Binding - private var onDragTabId: TabBarItemID? + private var onDragTabId: Item.ID? @Binding private var closeButtonGestureActive: Bool - /// The current WorkspaceDocument object. - /// - /// It contains the workspace-related information like selection states. @EnvironmentObject - private var workspace: WorkspaceDocument + private var tabs: TabGroupData /// The item associated with the current tab. /// /// You can get tab-related information from here, like `label`, `icon`, etc. - private var item: TabBarItemRepresentable + private var item: Item + + var index: Int private var isTemporary: Bool { - workspace.selectionState.temporaryTab == item.tabID + false + // TODO: Fix this + // workspace.selectionState.temporaryTab == item.tabID } /// Is the current tab the active tab. private var isActive: Bool { - item.tabID == workspace.selectionState.selectedId + item == tabs.selected } /// Is the current tab being dragged. private var isDragging: Bool { - draggingTabId == item.tabID + draggingTabId == item.id } /// Is the current tab being held (by click and hold, not drag). @@ -94,8 +97,8 @@ struct TabBarItemView: View { /// Switch the active tab to current tab. private func switchAction() { // Only set the `selectedId` when they are not equal to avoid performance issue for now. - if workspace.selectionState.selectedId != item.tabID { - workspace.selectionState.selectedId = item.tabID + if tabs.selected != item { + tabs.selected = item } } @@ -107,21 +110,23 @@ struct TabBarItemView: View { withAnimation( .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) ) { - workspace.closeTab(item: item.tabID) + tabs.files.remove(item) } } init( - expectedWidth: Binding, - item: TabBarItemRepresentable, - draggingTabId: Binding, - onDragTabId: Binding, + expectedWidth: CGFloat, + item: Item, + index: Int, + draggingTabId: Item.ID?, + onDragTabId: Item.ID?, closeButtonGestureActive: Binding ) { - self._expectedWidth = expectedWidth + self.expectedWidth = expectedWidth self.item = item - self._draggingTabId = draggingTabId - self._onDragTabId = onDragTabId + self.index = index + self.draggingTabId = draggingTabId + self.onDragTabId = onDragTabId self._closeButtonGestureActive = closeButtonGestureActive } @@ -136,7 +141,7 @@ struct TabBarItemView: View { .padding(.top, isActive && prefs.preferences.general.tabBarStyle == .native ? 1.22 : 0) // Tab content (icon and text). HStack(alignment: .center, spacing: 5) { - item.icon + Image(systemName: item.systemImage) .resizable() .aspectRatio(contentMode: .fit) .foregroundColor( @@ -145,7 +150,7 @@ struct TabBarItemView: View { : .secondary ) .frame(width: 12, height: 12) - Text(item.title) + Text(item.fileName) .font( isTemporary ? .system(size: 11.0).italic() @@ -179,16 +184,18 @@ struct TabBarItemView: View { // Using an invisible button to contain the keyboard shortcut is simply // because the keyboard shortcut has an unexpected bug when working with // custom buttonStyle. This is an workaround and it works as expected. - Button( - action: switchAction, - label: { EmptyView() } - ) - .frame(width: 0, height: 0) - .keyboardShortcut( - workspace.getTabKeyEquivalent(item: item), - modifiers: [.command] - ) - .hidden() + if index < 10 { + Button( + action: switchAction, + label: { EmptyView() } + ) + .frame(width: 0, height: 0) + .keyboardShortcut( + KeyEquivalent(Character(String(index))), + modifiers: [.command] + ) + .hidden() + } // Close Button TabBarItemCloseButton( isActive: isActive, @@ -257,7 +264,7 @@ struct TabBarItemView: View { .background { if prefs.preferences.general.tabBarStyle == .xcode { TabBarItemBackground(isActive: isActive, isPressing: isPressing, isDragging: isDragging) - .animation(.easeInOut(duration: 0.08), value: isHovering) + .animation(.easeInOut(duration: 0.08), value: isHovering) } else { if isFullscreen && isActive { TabBarNativeActiveMaterial() @@ -288,7 +295,8 @@ struct TabBarItemView: View { TapGesture(count: 2) .onEnded { _ in if isTemporary { - workspace.convertTemporaryTab() + // TODO: Fix this + // workspace.convertTemporaryTab() } } ) @@ -300,7 +308,7 @@ struct TabBarItemView: View { x: isAppeared || prefs.preferences.general.tabBarStyle == .native ? 0 : -14, y: 0 ) - .opacity(isAppeared && onDragTabId != item.tabID ? 1.0 : 0.0) + .opacity(isAppeared && onDragTabId != item.id ? 1.0 : 0.0) .zIndex( isActive ? (prefs.preferences.general.tabBarStyle == .native ? -1 : 2) @@ -328,18 +336,7 @@ struct TabBarItemView: View { } } } - .id(item.tabID) + .id(item.id) .tabBarContextMenu(item: item, isTemporary: isTemporary) } } -// swiftlint:enable type_body_length - -fileprivate extension WorkspaceDocument { - func getTabKeyEquivalent(item: TabBarItemRepresentable) -> KeyEquivalent { - for counter in 0..<9 where self.selectionState.openFileItems.count > counter && - self.selectionState.openFileItems[counter].tabID == item.tabID { - return KeyEquivalent.init(Character.init("\(counter + 1)")) - } - return "0" - } -} diff --git a/CodeEdit/Features/TabBar/Views/TabBarNative.swift b/CodeEdit/Features/Tabs/Views/TabBarNative.swift similarity index 100% rename from CodeEdit/Features/TabBar/Views/TabBarNative.swift rename to CodeEdit/Features/Tabs/Views/TabBarNative.swift diff --git a/CodeEdit/Features/TabBar/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift similarity index 90% rename from CodeEdit/Features/TabBar/Views/TabBarView.swift rename to CodeEdit/Features/Tabs/Views/TabBarView.swift index 186bbc6aa6..c262e149bd 100644 --- a/CodeEdit/Features/TabBar/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -13,6 +13,9 @@ import SwiftUI // swiftlint:disable file_length type_body_length // - TODO: TabBarItemView drop-outside event handler. struct TabBarView: View { + + typealias TabID = WorkspaceClient.FileItem.ID + /// The height of tab bar. /// I am not making it a private variable because it may need to be used in outside views. static let height = 28.0 @@ -24,8 +27,11 @@ struct TabBarView: View { private var activeState /// The workspace document. + // @EnvironmentObject + // private var workspace: WorkspaceFiles + @EnvironmentObject - private var workspace: WorkspaceDocument + private var tabs: TabGroupData /// The app preference. @StateObject @@ -35,10 +41,10 @@ struct TabBarView: View { /// /// It will be `nil` when there is no tab dragged currently. @State - private var draggingTabId: TabBarItemID? + private var draggingTabId: TabID? @State - private var onDragTabId: TabBarItemID? + private var onDragTabId: TabID? /// The start location of dragging. /// @@ -60,7 +66,7 @@ struct TabBarView: View { /// I am making a copy of it because using state will hugely improve the dragging performance. /// Updating ObservedObject too often will generate lags. @State - private var openedTabs: [TabBarItemID] = [] + private var openedTabs: [TabID] = [] /// A map of tab width. /// @@ -68,20 +74,20 @@ struct TabBarView: View { /// This is used to be added on the offset of current dragging tab in order to make a smooth /// dragging experience. @State - private var tabWidth: [TabBarItemID: CGFloat] = [:] + private var tabWidth: [TabID: CGFloat] = [:] /// A map of tab location (CGRect). /// /// All locations are measured dynamically. /// This is used to compute when we should swap two tabs based on current cursor location. @State - private var tabLocations: [TabBarItemID: CGRect] = [:] + private var tabLocations: [TabID: CGRect] = [:] /// A map of tab offsets. /// /// This is used to determine the tab offset of every tab (by their tab id) while dragging. @State - private var tabOffsets: [TabBarItemID: CGFloat] = [:] + private var tabOffsets: [TabID: CGFloat] = [:] /// The expected tab width in native tab bar style. /// @@ -123,7 +129,7 @@ struct TabBarView: View { private func updateExpectedTabWidth(proxy: GeometryProxy) { expectedTabWidth = max( // Equally divided size of a native tab. - (proxy.size.width + 1) / CGFloat(workspace.selectionState.openedTabs.count) + 1, + (proxy.size.width + 1) / CGFloat(tabs.files.count) + 1, // Min size of a native tab. CGFloat(140) ) @@ -132,7 +138,7 @@ struct TabBarView: View { // Disable the rule because this function is implementing the drag gesture and its animations. // It is fairly complicated, so ignore the function body length limitation for now. // swiftlint:disable function_body_length cyclomatic_complexity - private func makeTabDragGesture(id: TabBarItemID) -> some Gesture { + private func makeTabDragGesture(id: TabID) -> some Gesture { return DragGesture(minimumDistance: 2, coordinateSpace: .global) .onChanged({ value in if closeButtonGestureActive { @@ -237,14 +243,16 @@ struct TabBarView: View { // In order to avoid the lag due to the update of workspace state. DispatchQueue.main.asyncAfter(deadline: .now() + 0.40) { if draggingStartLocation == nil { - workspace.selectionState.openedTabs = openedTabs + tabs.files = .init(openedTabs.compactMap { id in + tabs.files.first { $0.id == id } + }) } } }) } // swiftlint:enable function_body_length cyclomatic_complexity - private func makeTabItemGeometryReader(id: TabBarItemID) -> some View { + private func makeTabItemGeometryReader(id: TabID) -> some View { GeometryReader { tabItemGeoReader in Rectangle() .foregroundColor(.clear) @@ -272,7 +280,7 @@ struct TabBarView: View { /// Called when the tab count changes or the temporary tab changes. /// - Parameter geometryProxy: The geometry proxy to calculate the new width using. private func updateForTabCountChange(geometryProxy: GeometryProxy) { - openedTabs = workspace.selectionState.openedTabs + openedTabs = tabs.files.map(\.id) // Only update the expected width when user is not hovering over tabs. // This should give users a better experience on closing multiple tabs continuously. @@ -295,13 +303,14 @@ struct TabBarView: View { alignment: .center, spacing: -1 // Negative spacing for overlapping the divider. ) { - ForEach(openedTabs, id: \.id) { id in - if let item = workspace.selectionState.getItemByTab(id: id) { + ForEach(Array(openedTabs.enumerated()), id: \.element) { index, id in + if let item = tabs.files.first { $0.id == id } { TabBarItemView( - expectedWidth: $expectedTabWidth, + expectedWidth: expectedTabWidth, item: item, - draggingTabId: $draggingTabId, - onDragTabId: $onDragTabId, + index: index, + draggingTabId: draggingTabId, + onDragTabId: onDragTabId, closeButtonGestureActive: $closeButtonGestureActive ) .frame(height: TabBarView.height) @@ -330,24 +339,25 @@ struct TabBarView: View { // This padding is to hide dividers at two ends under the accessory view divider. .padding(.horizontal, prefs.preferences.general.tabBarStyle == .native ? -1 : 0) .onAppear { - openedTabs = workspace.selectionState.openedTabs + openedTabs = tabs.files.map(\.id) // On view appeared, compute the initial expected width for tabs. updateExpectedTabWidth(proxy: geometryProxy) // On first tab appeared, jump to the corresponding position. - scrollReader.scrollTo(workspace.selectionState.selectedId) + scrollReader.scrollTo(tabs.selected) } // When selected tab is changed, scroll to it if possible. - .onChange(of: workspace.selectionState.selectedId) { targetId in + .onChange(of: tabs.selected) { targetId in guard let selectedId = targetId else { return } scrollReader.scrollTo(selectedId) } // When tabs are changing, re-compute the expected tab width. - .onChange(of: workspace.selectionState.openedTabs.count) { _ in + .onChange(of: tabs.files.count) { _ in updateForTabCountChange(geometryProxy: geometryProxy) } - .onChange(of: workspace.selectionState.temporaryTab, perform: { _ in - updateForTabCountChange(geometryProxy: geometryProxy) - }) + // TODO: Fix this + // .onChange(of: workspace.selectionState.temporaryTab, perform: { _ in + // updateForTabCountChange(geometryProxy: geometryProxy) + // }) // When window size changes, re-compute the expected tab width. .onChange(of: geometryProxy.size.width) { _ in updateExpectedTabWidth(proxy: geometryProxy) @@ -366,7 +376,7 @@ struct TabBarView: View { } // When there is no opened file, hide the scroll view, but keep the background. .opacity( - workspace.selectionState.openedTabs.isEmpty && workspace.selectionState.temporaryTab == nil + tabs.files.isEmpty // TODO: Fix this && workspace.selectionState.temporaryTab == nil ? 0.0 : 1.0 ) @@ -463,25 +473,25 @@ struct TabBarView: View { } private struct TabBarItemOnDropDelegate: DropDelegate { - private let currentTabId: TabBarItemID + private let currentTabId: TabID @Binding - private var openedTabs: [TabBarItemID] + private var openedTabs: [TabID] @Binding - private var onDragTabId: TabBarItemID? + private var onDragTabId: TabID? @Binding private var onDragLastLocation: CGPoint? @Binding private var isOnDragOverTabs: Bool @Binding - private var tabWidth: [TabBarItemID: CGFloat] + private var tabWidth: [TabID: CGFloat] public init( - currentTabId: TabBarItemID, - openedTabs: Binding<[TabBarItemID]>, - onDragTabId: Binding, + currentTabId: TabID, + openedTabs: Binding<[TabID]>, + onDragTabId: Binding, onDragLastLocation: Binding, isOnDragOverTabs: Binding, - tabWidth: Binding<[TabBarItemID: CGFloat]> + tabWidth: Binding<[TabID: CGFloat]> ) { self.currentTabId = currentTabId self._openedTabs = openedTabs diff --git a/CodeEdit/Features/TabBar/Views/TabBarXcode.swift b/CodeEdit/Features/Tabs/Views/TabBarXcode.swift similarity index 100% rename from CodeEdit/Features/TabBar/Views/TabBarXcode.swift rename to CodeEdit/Features/Tabs/Views/TabBarXcode.swift diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 1043cc98fe..690dc924cc 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -41,43 +41,19 @@ struct WorkspaceView: View { @Environment(\.colorScheme) var colorScheme - var noEditor: some View { - Text("No Editor") - .font(.system(size: 17)) - .foregroundColor(.secondary) - .frame(minHeight: 0) - .clipped() - } - - @ViewBuilder var tabContent: some View { - if let tabID = workspace.selectionState.selectedId { - switch tabID { - case .codeEditor: - WorkspaceCodeFileView() - case .extensionInstallation: - if let plugin = workspace.selectionState.selected as? Plugin { - ExtensionInstallationView(plugin: plugin) - .frame(alignment: .center) - } - } - } else { - noEditor - } - } - var body: some View { ZStack { if workspace.workspaceClient != nil, let model = workspace.statusBarModel { ZStack { - tabContent + EditorView(tabgroup: workspace.tabs) } .frame(maxWidth: .infinity, maxHeight: .infinity) - .safeAreaInset(edge: .top, spacing: 0) { - VStack(spacing: 0) { - TabBarView() - TabBarBottomDivider() - } - } +// .safeAreaInset(edge: .top, spacing: 0) { +// VStack(spacing: 0) { +// TabBarView() +// TabBarBottomDivider() +// } +// } .safeAreaInset(edge: .bottom) { StatusBarView() .environmentObject(model) From 01368531408ca0408a5202287e548eeaad63f186 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Thu, 23 Feb 2023 21:08:44 +0100 Subject: [PATCH 02/82] added custom splitview + some debug views Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 25 ++++++ .../xcshareddata/swiftpm/Package.resolved | 9 ++ .../Features/SplitView/EditorSplitView.swift | 86 +++++++++++++++++++ CodeEdit/Features/SplitView/EditorView.swift | 11 +-- CodeEdit/Features/SplitView/SplitView.swift | 46 ++++++++++ CodeEdit/WorkspaceView.swift | 2 +- 6 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 CodeEdit/Features/SplitView/EditorSplitView.swift create mode 100644 CodeEdit/Features/SplitView/SplitView.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index afcdaeb3ef..31e06c8ff5 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -328,6 +328,9 @@ 6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */; }; 6C48D8F42972DB1A00D6D205 /* Env+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */; }; 6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */; }; + 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; }; + 6C7256D729A3D7D000C2D3E0 /* EditorSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */; }; + 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */; }; 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */; }; 6C97EBCF297876E500302F95 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCE297876E500302F95 /* AboutWindowController.swift */; }; 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */; }; @@ -725,6 +728,8 @@ 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+IsFullscreen.swift"; sourceTree = ""; }; 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+Window.swift"; sourceTree = ""; }; 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = ""; }; + 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; + 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorSplitView.swift; sourceTree = ""; }; 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; 6C97EBCE297876E500302F95 /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Caption3.swift"; sourceTree = ""; }; @@ -779,6 +784,7 @@ 5879826B292EC7B00085B254 /* Light-Swift-Untar in Frameworks */, 5879828A292ED15F0085B254 /* SwiftTerm in Frameworks */, 2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */, + 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2238,6 +2244,8 @@ 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */, 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */, 6C147C4829A32A080089B630 /* EditorView.swift */, + 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */, + 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */, ); path = SplitView; sourceTree = ""; @@ -2407,6 +2415,7 @@ 58F2EB19292FB91C004A9BDE /* Preferences */, 58F2EB1D292FB954004A9BDE /* Sparkle */, 6C147C4429A329350089B630 /* OrderedCollections */, + 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */, ); productName = CodeEdit; productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */; @@ -2501,6 +2510,7 @@ 58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference "Sparkle" */, 583E529A29361BAB001AB554 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */, + 6C7256D829A3D98C00C2D3E0 /* XCRemoteSwiftPackageReference "SequenceBuilder" */, ); productRefGroup = B658FB2D27DA9E0F00EA4DBD /* Products */; projectDirPath = ""; @@ -2674,6 +2684,7 @@ 58F2EAF4292FB2B0004A9BDE /* ThemeModel.swift in Sources */, 58F2EB0D292FB2B0004A9BDE /* ThemePreferences.swift in Sources */, 587B9D9F29300ABD00AC7927 /* SegmentedControl.swift in Sources */, + 6C7256D729A3D7D000C2D3E0 /* EditorSplitView.swift in Sources */, 58FD7609291EA1CB0051D6E4 /* CommandPaletteView.swift in Sources */, 58F2EAFC292FB2B0004A9BDE /* GitAccountItemView.swift in Sources */, 587B9E8F29301D8F00AC7927 /* BitBucketUserRouter.swift in Sources */, @@ -2777,6 +2788,7 @@ 58F2EB12292FB2B0004A9BDE /* PreferencesPlaceholderView.swift in Sources */, 58822526292C280D00E83CDE /* StatusBarBreakpointButton.swift in Sources */, 58D01C96293167DC00C5B6B4 /* Date+Formatted.swift in Sources */, + 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */, 587B9D9E29300ABD00AC7927 /* FontPickerView.swift in Sources */, 6C97EBCF297876E500302F95 /* AboutWindowController.swift in Sources */, 58822529292C280D00E83CDE /* StatusBarLineEndSelector.swift in Sources */, @@ -3881,6 +3893,14 @@ minimumVersion = 1.0.0; }; }; + 6C7256D829A3D98C00C2D3E0 /* XCRemoteSwiftPackageReference "SequenceBuilder" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/andtie/SequenceBuilder"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.0.7; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3939,6 +3959,11 @@ package = 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = OrderedCollections; }; + 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */ = { + isa = XCSwiftPackageProductDependency; + package = 6C7256D829A3D98C00C2D3E0 /* XCRemoteSwiftPackageReference "SequenceBuilder" */; + productName = SequenceBuilder; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B658FB2427DA9E0F00EA4DBD /* Project object */; diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c1ede28905..12f03f8086 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -72,6 +72,15 @@ "version" : "1.5.3" } }, + { + "identity" : "sequencebuilder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/andtie/SequenceBuilder", + "state" : { + "revision" : "54d3d1eff31a7e35122f616840fff11899ea85b4", + "version" : "0.0.7" + } + }, { "identity" : "sparkle", "kind" : "remoteSourceControl", diff --git a/CodeEdit/Features/SplitView/EditorSplitView.swift b/CodeEdit/Features/SplitView/EditorSplitView.swift new file mode 100644 index 0000000000..c32ef8a766 --- /dev/null +++ b/CodeEdit/Features/SplitView/EditorSplitView.swift @@ -0,0 +1,86 @@ +// +// EditorSplitView.swift +// CodeEdit +// +// Created by Wouter Hennen on 20/02/2023. +// + +import SwiftUI + +struct SplitViewItem: Hashable { + var id: AnyHashable + var item: NSSplitViewItem + + init(id: AnyHashable, controller: NSViewController) { + self.id = id + self.item = .init(viewController: controller) + self.item.minimumThickness = 200 + self.item.canCollapse = false + } +} + +struct EditorSplitView: NSViewControllerRepresentable { + + var axis: Axis + + var children: _VariadicView.Children + + var splitPosition: CGFloat + + func makeNSViewController(context: Context) -> SplitViewController { + let controller = SplitViewController(childrenn: children, axis: axis) + + return controller + } + + func updateNSViewController(_ controller: SplitViewController, context: Context) { + print("Update!") + + // Reorder viewcontrollers if needed and add new ones. + var hasChanged = false + controller.items = children.map { child in + if let item = controller.items.first(where: { $0.id == child.id }) { + return item + } else { + hasChanged = true + return SplitViewItem(id: child.id, controller: NSHostingController(rootView: child)) + } + } + + controller.splitViewItems = controller.items.map(\.item) + + if hasChanged && controller.splitViewItems.count > 1 { + controller.splitView.setPosition(splitPosition, ofDividerAt: 0) + controller.splitView.layoutSubtreeIfNeeded() + controller.splitView.adjustSubviews() + } + } +} + +final class SplitViewController: NSSplitViewController { + + var items: [SplitViewItem] = [] + + var axis: Axis + + var childrenn: _VariadicView.Children + + init(childrenn: _VariadicView.Children, axis: Axis = .horizontal) { + self.childrenn = childrenn + self.axis = axis + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + splitView.isVertical = axis != .vertical + splitView.dividerStyle = .thick + } + + override func splitView(_ splitView: NSSplitView, shouldHideDividerAt dividerIndex: Int) -> Bool { + false + } +} diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index 0bff836de2..cb8e2224f0 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -23,15 +23,8 @@ struct EditorView: View { @ObservedObject var data: WorkspaceSplitViewData var body: some View { - switch data.axis { - case .vertical: - VSplitView { - splitView - } - case .horizontal: - HSplitView { - splitView - } + SplitView(axis: data.axis) { + splitView } } diff --git a/CodeEdit/Features/SplitView/SplitView.swift b/CodeEdit/Features/SplitView/SplitView.swift new file mode 100644 index 0000000000..74be81dabe --- /dev/null +++ b/CodeEdit/Features/SplitView/SplitView.swift @@ -0,0 +1,46 @@ +// +// SequenceView.swift +// CodeEdit +// +// Created by Wouter Hennen on 22/02/2023. +// + +import SwiftUI + +// swiftlint:disable identifier_name +struct Helper: _VariadicView_MultiViewRoot { + var _body: (_VariadicView.Children) -> Result + + func body(children: _VariadicView.Children) -> some View { + _body(children) + } +} + +extension View { + func variadic(@ViewBuilder process: @escaping (_VariadicView.Children) -> R) -> some View { + _VariadicView.Tree(Helper(_body: process), content: { self }) + } +} + +struct SplitView: View { + var axis: Axis + + @ViewBuilder var content: Content + + @State private var splitPosition = 1.0 + + var body: some View { + VStack { + HStack { + Stepper("Split position \(splitPosition)", value: $splitPosition, in: 0.0...500.0, step: 0.5) + Slider(value: $splitPosition, in: 0.0...500.0) + .frame(width: 200) + } + content.variadic { children in + EditorSplitView(axis: axis, children: children, splitPosition: splitPosition) + .frame(maxWidth: .infinity, maxHeight: .infinity) + + } + } + } +} diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 690dc924cc..151a0577e8 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -45,7 +45,7 @@ struct WorkspaceView: View { ZStack { if workspace.workspaceClient != nil, let model = workspace.statusBarModel { ZStack { - EditorView(tabgroup: workspace.tabs) + EditorView(tabgroup: workspace.tabs).id(UUID()) } .frame(maxWidth: .infinity, maxHeight: .infinity) // .safeAreaInset(edge: .top, spacing: 0) { From 345ae0ec2003ed4b52c3ff4c648344b05e24b808 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 23 Feb 2023 17:38:06 -0600 Subject: [PATCH 03/82] Add even splitting on new tabs --- .../Features/SplitView/EditorSplitView.swift | 33 +++++++++---------- CodeEdit/Features/SplitView/SplitView.swift | 11 +------ 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorSplitView.swift b/CodeEdit/Features/SplitView/EditorSplitView.swift index c32ef8a766..f0cf882494 100644 --- a/CodeEdit/Features/SplitView/EditorSplitView.swift +++ b/CodeEdit/Features/SplitView/EditorSplitView.swift @@ -14,28 +14,23 @@ struct SplitViewItem: Hashable { init(id: AnyHashable, controller: NSViewController) { self.id = id self.item = .init(viewController: controller) - self.item.minimumThickness = 200 - self.item.canCollapse = false + item.minimumThickness = 200 + item.canCollapse = false } } struct EditorSplitView: NSViewControllerRepresentable { var axis: Axis - var children: _VariadicView.Children - var splitPosition: CGFloat - func makeNSViewController(context: Context) -> SplitViewController { - let controller = SplitViewController(childrenn: children, axis: axis) + let controller = SplitViewController(variadicChildren: children, axis: axis) return controller } func updateNSViewController(_ controller: SplitViewController, context: Context) { - print("Update!") - // Reorder viewcontrollers if needed and add new ones. var hasChanged = false controller.items = children.map { child in @@ -50,9 +45,15 @@ struct EditorSplitView: NSViewControllerRepresentable { controller.splitViewItems = controller.items.map(\.item) if hasChanged && controller.splitViewItems.count > 1 { - controller.splitView.setPosition(splitPosition, ofDividerAt: 0) - controller.splitView.layoutSubtreeIfNeeded() - controller.splitView.adjustSubviews() + print(controller.items.count, controller.splitView.frame.width) + print(controller.splitView.frame.width / CGFloat(controller.items.count)) + + for idx in 0.. Bool { diff --git a/CodeEdit/Features/SplitView/SplitView.swift b/CodeEdit/Features/SplitView/SplitView.swift index 74be81dabe..ce757d2e6e 100644 --- a/CodeEdit/Features/SplitView/SplitView.swift +++ b/CodeEdit/Features/SplitView/SplitView.swift @@ -27,19 +27,10 @@ struct SplitView: View { @ViewBuilder var content: Content - @State private var splitPosition = 1.0 - var body: some View { VStack { - HStack { - Stepper("Split position \(splitPosition)", value: $splitPosition, in: 0.0...500.0, step: 0.5) - Slider(value: $splitPosition, in: 0.0...500.0) - .frame(width: 200) - } content.variadic { children in - EditorSplitView(axis: axis, children: children, splitPosition: splitPosition) - .frame(maxWidth: .infinity, maxHeight: .infinity) - + EditorSplitView(axis: axis, children: children) } } } From 2f29821ffa0f67fdd33d8995a0ef422b05ebb6cb Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 24 Feb 2023 01:33:14 +0100 Subject: [PATCH 04/82] removed unused variable in SplitViewController Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/EditorSplitView.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorSplitView.swift b/CodeEdit/Features/SplitView/EditorSplitView.swift index f0cf882494..648b11b34a 100644 --- a/CodeEdit/Features/SplitView/EditorSplitView.swift +++ b/CodeEdit/Features/SplitView/EditorSplitView.swift @@ -25,8 +25,7 @@ struct EditorSplitView: NSViewControllerRepresentable { var children: _VariadicView.Children func makeNSViewController(context: Context) -> SplitViewController { - let controller = SplitViewController(variadicChildren: children, axis: axis) - + let controller = SplitViewController(axis: axis) return controller } @@ -62,10 +61,8 @@ final class SplitViewController: NSSplitViewController { var items: [SplitViewItem] = [] var axis: Axis - var variadicChildren: _VariadicView.Children - init(variadicChildren: _VariadicView.Children, axis: Axis = .horizontal) { - self.variadicChildren = variadicChildren + init(axis: Axis = .horizontal) { self.axis = axis super.init(nibName: nil, bundle: nil) } From 221841b7c48a21865a1d8467cad95c1583367aef Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 24 Feb 2023 02:37:42 +0100 Subject: [PATCH 05/82] Added tab selection logic Signed-off-by: Wouter01 --- .../Features/Tabs/TabGroup/TabGroupData.swift | 22 +++++++++++++++++-- .../Tabs/Views/TabBarContextMenu.swift | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 1ad2dccf00..37c131f90f 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -9,11 +9,29 @@ import Foundation import OrderedCollections final class TabGroupData: ObservableObject { - @Published var files: OrderedSet = [] + @Published var files: OrderedSet = [] { + didSet { + let change = files.symmetricDifference(oldValue) + + if files.count > oldValue.count { + // Amount of tabs grew, so set the first new as selected. + selected = change.first + } else { + // Selected file was removed + if let selected, change.contains(selected) { + if let oldIndex = oldValue.firstIndex(of: selected), oldIndex - 1 < files.count, !files.isEmpty { + self.selected = files[max(0, oldIndex-1)] + } else { + self.selected = nil + } + } + } + } + } @Published var selected: WorkspaceClient.FileItem? init(files: OrderedSet = [], selected: WorkspaceClient.FileItem? = nil) { self.files = files - self.selected = selected + self.selected = selected ?? files.first } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index d4015a1647..04170a20d8 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -126,7 +126,7 @@ struct TabBarContextMenu: ViewModifier { } func moveToNewSplit(_ edge: Edge) { - let newTabGroup = TabGroupData(files: [item], selected: item) + let newTabGroup = TabGroupData(files: [item]) splitEditor(edge, newTabGroup) tabs.files.remove(item) } From 88daa71d054f3e26741d0e06a58ec0fbd9e0423b Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 24 Feb 2023 04:51:04 +0100 Subject: [PATCH 06/82] partially fixed edgeinsets Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 4 +++ .../xcshareddata/swiftpm/Package.resolved | 4 +-- .../Breadcrumbs/Views/BreadcrumbsView.swift | 6 +++-- CodeEdit/Features/CodeFile/CodeFileView.swift | 5 +++- CodeEdit/Features/SplitView/EditorView.swift | 1 + .../SplitView/Environment+ContentInsets.swift | 25 +++++++++++++++++++ CodeEdit/Features/SplitView/SplitView.swift | 6 ++--- .../SplitView/WorkspaceTabGroupView.swift | 13 +++++++++- 8 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 CodeEdit/Features/SplitView/Environment+ContentInsets.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index a531a5b3ee..f570478744 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -321,6 +321,7 @@ 6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */; }; 6C48D8F42972DB1A00D6D205 /* Env+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */; }; 6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */; }; + 6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */; }; 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; }; 6C7256D729A3D7D000C2D3E0 /* EditorSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */; }; 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */; }; @@ -713,6 +714,7 @@ 6C48D8F12972DAFC00D6D205 /* Env+IsFullscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+IsFullscreen.swift"; sourceTree = ""; }; 6C48D8F32972DB1A00D6D205 /* Env+Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+Window.swift"; sourceTree = ""; }; 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = ""; }; + 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ContentInsets.swift"; sourceTree = ""; }; 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorSplitView.swift; sourceTree = ""; }; 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; @@ -2196,6 +2198,7 @@ children = ( 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */, 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */, + 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */, 6C147C4829A32A080089B630 /* EditorView.swift */, 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */, 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */, @@ -2664,6 +2667,7 @@ D7E201B027E8C07300CB86D0 /* FindNavigatorSearchBar.swift in Sources */, 58798237292E30B90085B254 /* FeedbackView.swift in Sources */, 587B9E9829301D8F00AC7927 /* GitCommit.swift in Sources */, + 6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */, 587B9E9429301D8F00AC7927 /* BitBucketTokenConfiguration.swift in Sources */, 581BFB672926431000D251EC /* WelcomeWindowView.swift in Sources */, 58A5DFA329339F6400D1BD5D /* CommandManager.swift in Sources */, diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 12f03f8086..8a58ec3753 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/krzyzanowskim/STTextView.git", "state" : { - "revision" : "41c7c87a552e6286ebea5f348e820b529c6f5662", - "version" : "0.4.2" + "revision" : "d6bec568df43028352c4062215fc3b5fab07c2d2", + "version" : "0.4.3" } }, { diff --git a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift index 4244e2260b..d8018334c5 100644 --- a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift +++ b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift @@ -18,6 +18,8 @@ struct BreadcrumbsView: View { @Environment(\.controlActiveState) private var activeState + static let height = 28.0 + init( file: WorkspaceClient.FileItem, tappedOpenFile: @escaping (WorkspaceClient.FileItem) -> Void @@ -51,8 +53,8 @@ struct BreadcrumbsView: View { } .padding(.horizontal, 10) } - .frame(height: 28, alignment: .center) - .background(EffectView(.headerView).frame(height: 28)) + .frame(height: Self.height, alignment: .center) + .background(EffectView(.headerView).frame(height: Self.height)) } private var chevron: some View { diff --git a/CodeEdit/Features/CodeFile/CodeFileView.swift b/CodeEdit/Features/CodeFile/CodeFileView.swift index fe19c8c8fc..909fe99e20 100644 --- a/CodeEdit/Features/CodeFile/CodeFileView.swift +++ b/CodeEdit/Features/CodeFile/CodeFileView.swift @@ -60,6 +60,8 @@ struct CodeFileView: View { return AppPreferencesModel.shared.preferences.textEditing.font.current() }() + @Environment(\.edgeInsets) var edgeInsets + var body: some View { CodeEditTextView( $codeFile.content, @@ -70,7 +72,8 @@ struct CodeFileView: View { lineHeight: $prefs.preferences.textEditing.lineHeightMultiple, wrapLines: $prefs.preferences.textEditing.wrapLinesToEditorWidth, cursorPosition: codeFile.$cursorPosition, - useThemeBackground: prefs.preferences.theme.useThemeBackground + useThemeBackground: prefs.preferences.theme.useThemeBackground, + contentInsets: edgeInsets.nsEdgeInsets ) .id(codeFile.fileURL) .background { diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index cb8e2224f0..a52b262f63 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -26,6 +26,7 @@ struct EditorView: View { SplitView(axis: data.axis) { splitView } + .edgesIgnoringSafeArea(.top) } var splitView: some View { diff --git a/CodeEdit/Features/SplitView/Environment+ContentInsets.swift b/CodeEdit/Features/SplitView/Environment+ContentInsets.swift new file mode 100644 index 0000000000..449f88e707 --- /dev/null +++ b/CodeEdit/Features/SplitView/Environment+ContentInsets.swift @@ -0,0 +1,25 @@ +// +// Environment+ContentInsets.swift +// CodeEdit +// +// Created by Wouter Hennen on 24/02/2023. +// + +import SwiftUI + +struct EdgeInsetsEnvironmentKey: EnvironmentKey { + static var defaultValue: EdgeInsets = .init() +} + +extension EnvironmentValues { + var edgeInsets: EdgeInsetsEnvironmentKey.Value { + get { self[EdgeInsetsEnvironmentKey.self] } + set { self[EdgeInsetsEnvironmentKey.self] = newValue } + } +} + +extension EdgeInsets { + var nsEdgeInsets: NSEdgeInsets { + .init(top: top, left: leading, bottom: bottom, right: trailing) + } +} diff --git a/CodeEdit/Features/SplitView/SplitView.swift b/CodeEdit/Features/SplitView/SplitView.swift index ce757d2e6e..f6ef4c2d63 100644 --- a/CodeEdit/Features/SplitView/SplitView.swift +++ b/CodeEdit/Features/SplitView/SplitView.swift @@ -28,10 +28,8 @@ struct SplitView: View { @ViewBuilder var content: Content var body: some View { - VStack { - content.variadic { children in - EditorSplitView(axis: axis, children: children) - } + content.variadic { children in + EditorSplitView(axis: axis, children: children) } } } diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index bcfe4a1811..61c2cca27d 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -10,6 +10,16 @@ import SwiftUI struct WorkspaceTabGroupView: View { @ObservedObject var tabgroup: TabGroupData + @Environment(\.window) var window + + var toolbarHeight: CGFloat { + window.contentView?.safeAreaInsets.top ?? .zero + } + + var edgeInsets: NSEdgeInsets { + .init(top: toolbarHeight + TabBarView.height + BreadcrumbsView.height + 1 + 1, leading: 0, bottom: 0, trailing: 0) + } + var body: some View { VStack { if let selected = tabgroup.selected { @@ -28,11 +38,12 @@ struct WorkspaceTabGroupView: View { } .frame(maxWidth: .infinity) .ignoresSafeArea(.all) + .environment(\.edgeInsets, edgeInsets) .safeAreaInset(edge: .top, spacing: 0) { VStack(spacing: 0) { TabBarView() .environmentObject(tabgroup) - + Divider() if let file = tabgroup.selected { BreadcrumbsView(file: file) { newFile in From 9015e32886564a7fea72ce02fc7879b49c9c72da Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 24 Feb 2023 05:17:09 +0100 Subject: [PATCH 07/82] Fixed top toolbar edgeinsets Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/EditorView.swift | 19 ++++++++++++++++--- .../SplitView/WorkspaceTabGroupView.swift | 10 +++++++--- CodeEdit/WorkspaceView.swift | 2 +- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index a52b262f63..92e389456c 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -10,18 +10,22 @@ import SwiftUI struct EditorView: View { var tabgroup: TabGroup + var isBelowToolbar = false + var body: some View { switch tabgroup { case .one(let detailTabGroup): - WorkspaceTabGroupView(tabgroup: detailTabGroup) + WorkspaceTabGroupView(tabgroup: detailTabGroup, isBelowToolbar: isBelowToolbar) case .vertical(let data), .horizontal(let data): - SubEditorView(data: data) + SubEditorView(data: data, isBelowToolbar: isBelowToolbar) } } struct SubEditorView: View { @ObservedObject var data: WorkspaceSplitViewData + var isBelowToolbar = false + var body: some View { SplitView(axis: data.axis) { splitView @@ -31,11 +35,20 @@ struct EditorView: View { var splitView: some View { ForEach(Array(data.tabgroups.enumerated()), id: \.offset) { index, item in - EditorView(tabgroup: item) + EditorView(tabgroup: item, isBelowToolbar: calcIsBelowToolbar(index: index)) .environment(\.splitEditor) { edge, newTabGroup in data.split(edge, at: index, new: newTabGroup) } } } + + func calcIsBelowToolbar(index: Int) -> Bool { + switch data.axis { + case .horizontal: + return isBelowToolbar + case .vertical: + return isBelowToolbar && index == .zero + } + } } } diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index 61c2cca27d..af74a2ea3d 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -10,14 +10,18 @@ import SwiftUI struct WorkspaceTabGroupView: View { @ObservedObject var tabgroup: TabGroupData - @Environment(\.window) var window + var isBelowToolbar = false + + @Environment(\.window) private var window var toolbarHeight: CGFloat { window.contentView?.safeAreaInsets.top ?? .zero } - var edgeInsets: NSEdgeInsets { - .init(top: toolbarHeight + TabBarView.height + BreadcrumbsView.height + 1 + 1, leading: 0, bottom: 0, trailing: 0) + var edgeInsets: EdgeInsets { + let top = TabBarView.height + BreadcrumbsView.height + 1 + 1 + let extraHeight = isBelowToolbar ? toolbarHeight : .zero + return EdgeInsets(top: top + extraHeight, leading: 0, bottom: 0, trailing: 0) } var body: some View { diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 151a0577e8..4faec332b3 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -45,7 +45,7 @@ struct WorkspaceView: View { ZStack { if workspace.workspaceClient != nil, let model = workspace.statusBarModel { ZStack { - EditorView(tabgroup: workspace.tabs).id(UUID()) + EditorView(tabgroup: workspace.tabs, isBelowToolbar: true).id(UUID()) } .frame(maxWidth: .infinity, maxHeight: .infinity) // .safeAreaInset(edge: .top, spacing: 0) { From 080039a7b9fc3776774d1cf7b4d0936fe8341c45 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 24 Feb 2023 05:53:36 +0100 Subject: [PATCH 08/82] Moved isBelowToolbar to environment Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/EditorView.swift | 37 +++++++++++++++---- .../SplitView/WorkspaceTabGroupView.swift | 18 ++------- CodeEdit/WorkspaceView.swift | 2 +- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index 92e389456c..33100c257d 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -10,22 +10,31 @@ import SwiftUI struct EditorView: View { var tabgroup: TabGroup - var isBelowToolbar = false + @Environment(\.window) private var window + + @Environment(\.isBelowToolbar) private var isBelowToolbar + + var toolbarHeight: CGFloat { + window.contentView?.safeAreaInsets.top ?? .zero + } var body: some View { switch tabgroup { case .one(let detailTabGroup): - WorkspaceTabGroupView(tabgroup: detailTabGroup, isBelowToolbar: isBelowToolbar) + WorkspaceTabGroupView(tabgroup: detailTabGroup) + .transformEnvironment(\.edgeInsets) { insets in + if isBelowToolbar { + insets.top += toolbarHeight + } + } case .vertical(let data), .horizontal(let data): - SubEditorView(data: data, isBelowToolbar: isBelowToolbar) + SubEditorView(data: data) } } struct SubEditorView: View { @ObservedObject var data: WorkspaceSplitViewData - var isBelowToolbar = false - var body: some View { SplitView(axis: data.axis) { splitView @@ -35,14 +44,17 @@ struct EditorView: View { var splitView: some View { ForEach(Array(data.tabgroups.enumerated()), id: \.offset) { index, item in - EditorView(tabgroup: item, isBelowToolbar: calcIsBelowToolbar(index: index)) + EditorView(tabgroup: item) + .transformEnvironment(\.isBelowToolbar, transform: { belowToolbar in + belowToolbar = calcIsBelowToolbar(isBelowToolbar: belowToolbar, index: index) + }) .environment(\.splitEditor) { edge, newTabGroup in data.split(edge, at: index, new: newTabGroup) } } } - func calcIsBelowToolbar(index: Int) -> Bool { + func calcIsBelowToolbar(isBelowToolbar: Bool, index: Int) -> Bool { switch data.axis { case .horizontal: return isBelowToolbar @@ -52,3 +64,14 @@ struct EditorView: View { } } } + +private struct BelowToolbarEnvironmentKey: EnvironmentKey { + static var defaultValue = true +} + +extension EnvironmentValues { + fileprivate var isBelowToolbar: BelowToolbarEnvironmentKey.Value { + get { self[BelowToolbarEnvironmentKey.self] } + set { self[BelowToolbarEnvironmentKey.self] = newValue } + } +} diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index af74a2ea3d..526d5fdcab 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -10,24 +10,13 @@ import SwiftUI struct WorkspaceTabGroupView: View { @ObservedObject var tabgroup: TabGroupData - var isBelowToolbar = false - - @Environment(\.window) private var window - - var toolbarHeight: CGFloat { - window.contentView?.safeAreaInsets.top ?? .zero - } - - var edgeInsets: EdgeInsets { - let top = TabBarView.height + BreadcrumbsView.height + 1 + 1 - let extraHeight = isBelowToolbar ? toolbarHeight : .zero - return EdgeInsets(top: top + extraHeight, leading: 0, bottom: 0, trailing: 0) - } - var body: some View { VStack { if let selected = tabgroup.selected { WorkspaceCodeFileView(file: selected) + .transformEnvironment(\.edgeInsets) { insets in + insets.top += TabBarView.height + BreadcrumbsView.height + 1 + 1 + } } else { VStack { Spacer() @@ -42,7 +31,6 @@ struct WorkspaceTabGroupView: View { } .frame(maxWidth: .infinity) .ignoresSafeArea(.all) - .environment(\.edgeInsets, edgeInsets) .safeAreaInset(edge: .top, spacing: 0) { VStack(spacing: 0) { TabBarView() diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 4faec332b3..151a0577e8 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -45,7 +45,7 @@ struct WorkspaceView: View { ZStack { if workspace.workspaceClient != nil, let model = workspace.statusBarModel { ZStack { - EditorView(tabgroup: workspace.tabs, isBelowToolbar: true).id(UUID()) + EditorView(tabgroup: workspace.tabs).id(UUID()) } .frame(maxWidth: .infinity, maxHeight: .infinity) // .safeAreaInset(edge: .top, spacing: 0) { From e98c80235d6e009d3158a0954ecce18003c5f74c Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 24 Feb 2023 18:39:49 +0100 Subject: [PATCH 09/82] Partial support for statusbarview (expanded wip) Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/EditorView.swift | 40 ++++++++++++------- .../StatusBar/Views/StatusBarView.swift | 4 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index 33100c257d..569a488e2c 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -12,7 +12,7 @@ struct EditorView: View { @Environment(\.window) private var window - @Environment(\.isBelowToolbar) private var isBelowToolbar + @Environment(\.isAtEdge) private var isAtEdge var toolbarHeight: CGFloat { window.contentView?.safeAreaInsets.top ?? .zero @@ -23,8 +23,16 @@ struct EditorView: View { case .one(let detailTabGroup): WorkspaceTabGroupView(tabgroup: detailTabGroup) .transformEnvironment(\.edgeInsets) { insets in - if isBelowToolbar { + switch isAtEdge { + case .all: insets.top += toolbarHeight + insets.bottom += StatusBarView.height + case .top: + insets.top += toolbarHeight + case .bottom: + insets.bottom += StatusBarView.height + default: + return } } case .vertical(let data), .horizontal(let data): @@ -39,38 +47,42 @@ struct EditorView: View { SplitView(axis: data.axis) { splitView } - .edgesIgnoringSafeArea(.top) + .edgesIgnoringSafeArea([.top, .bottom]) } var splitView: some View { ForEach(Array(data.tabgroups.enumerated()), id: \.offset) { index, item in EditorView(tabgroup: item) - .transformEnvironment(\.isBelowToolbar, transform: { belowToolbar in - belowToolbar = calcIsBelowToolbar(isBelowToolbar: belowToolbar, index: index) - }) + .transformEnvironment(\.isAtEdge) { belowToolbar in + calcIsAtEdge(current: &belowToolbar, index: index) + } .environment(\.splitEditor) { edge, newTabGroup in data.split(edge, at: index, new: newTabGroup) } } } - func calcIsBelowToolbar(isBelowToolbar: Bool, index: Int) -> Bool { - switch data.axis { - case .horizontal: - return isBelowToolbar - case .vertical: - return isBelowToolbar && index == .zero + func calcIsAtEdge(current: inout VerticalEdge.Set, index: Int) { + if case .vertical = data.axis { + guard data.tabgroups.count != 1 else { return } + if index == data.tabgroups.count - 1 { + current.remove(.top) + } else if index == 0 { + current.remove(.bottom) + } else { + current = [] + } } } } } private struct BelowToolbarEnvironmentKey: EnvironmentKey { - static var defaultValue = true + static var defaultValue: VerticalEdge.Set = .all } extension EnvironmentValues { - fileprivate var isBelowToolbar: BelowToolbarEnvironmentKey.Value { + fileprivate var isAtEdge: BelowToolbarEnvironmentKey.Value { get { self[BelowToolbarEnvironmentKey.self] } set { self[BelowToolbarEnvironmentKey.self] = newValue } } diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index 0cffcfa17c..5e33038495 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -23,6 +23,8 @@ struct StatusBarView: View { @EnvironmentObject private var model: StatusBarViewModel + static let height = 29.0 + var body: some View { VStack(spacing: 0) { bar @@ -67,7 +69,7 @@ struct StatusBarView: View { PanelDivider() } } - .frame(height: 29) + .frame(height: Self.height) .gesture(dragGesture) .onHover { isHovering($0, isDragging: model.isDragging, cursor: .resizeUpDown) } } From bea430ea3dd1d23e3853fc6d799be7f0607d58f6 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 27 Feb 2023 04:35:15 +0100 Subject: [PATCH 10/82] Fixed terminal view Signed-off-by: Wouter01 --- .../Features/SplitView/EditorSplitView.swift | 62 +++++++++--- CodeEdit/Features/SplitView/EditorView.swift | 4 +- CodeEdit/Features/SplitView/SplitView.swift | 99 ++++++++++++++++++- .../StatusBarToggleDrawerButton.swift | 11 ++- .../StatusBar/Views/StatusBarView.swift | 86 ++++++++-------- CodeEdit/WorkspaceView.swift | 51 +++++++--- 6 files changed, 237 insertions(+), 76 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorSplitView.swift b/CodeEdit/Features/SplitView/EditorSplitView.swift index 648b11b34a..4730be80d1 100644 --- a/CodeEdit/Features/SplitView/EditorSplitView.swift +++ b/CodeEdit/Features/SplitView/EditorSplitView.swift @@ -6,39 +6,68 @@ // import SwiftUI +import Combine + +class SplitViewItem: ObservableObject { -struct SplitViewItem: Hashable { var id: AnyHashable var item: NSSplitViewItem - init(id: AnyHashable, controller: NSViewController) { - self.id = id - self.item = .init(viewController: controller) - item.minimumThickness = 200 - item.canCollapse = false + var collapsed: Binding + + var cancellables: [AnyCancellable] = [] + + var observers: [NSKeyValueObservation] = [] + + init(child: _VariadicView.Children.Element) { + self.id = child.id + self.item = NSSplitViewItem(viewController: NSHostingController(rootView: child)) + self.collapsed = child[SplitViewItemCollapsedViewTraitKey.self] + self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] + self.observers = createObservers() + } + + func createObservers() -> [NSKeyValueObservation] { + [ + item.observe(\.isCollapsed) { item, _ in + self.collapsed.wrappedValue = item.isCollapsed + } + ] + } + + func update(child: _VariadicView.Children.Element) { + self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] + DispatchQueue.main.async { + self.observers = [] + self.item.animator().isCollapsed = child[SplitViewItemCollapsedViewTraitKey.self].wrappedValue + self.observers = self.createObservers() + } } } struct EditorSplitView: NSViewControllerRepresentable { - var axis: Axis var children: _VariadicView.Children + var viewController: SplitViewController func makeNSViewController(context: Context) -> SplitViewController { - let controller = SplitViewController(axis: axis) - return controller + return viewController } func updateNSViewController(_ controller: SplitViewController, context: Context) { + print("Update") // Reorder viewcontrollers if needed and add new ones. var hasChanged = false controller.items = children.map { child in - if let item = controller.items.first(where: { $0.id == child.id }) { - return item + let item: SplitViewItem + if let foundItem = controller.items.first(where: { $0.id == child.id }) { + item = foundItem + item.update(child: child) } else { hasChanged = true - return SplitViewItem(id: child.id, controller: NSHostingController(rootView: child)) + item = SplitViewItem(child: child) } + return item } controller.splitViewItems = controller.items.map(\.item) @@ -72,6 +101,7 @@ final class SplitViewController: NSSplitViewController { } override func viewDidLoad() { +// splitView.arrangesAllSubviews = false splitView.isVertical = axis != .vertical splitView.dividerStyle = .thin } @@ -79,4 +109,12 @@ final class SplitViewController: NSSplitViewController { override func splitView(_ splitView: NSSplitView, shouldHideDividerAt dividerIndex: Int) -> Bool { false } + + func collapse(for id: AnyHashable, enabled: Bool) { + items.first { $0.id == id }?.item.animator().isCollapsed = enabled + } + +// override func splitViewDidResizeSubviews(_ notification: Notification) { +// print(notification) +// } } diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index 569a488e2c..135545963b 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -26,11 +26,11 @@ struct EditorView: View { switch isAtEdge { case .all: insets.top += toolbarHeight - insets.bottom += StatusBarView.height + insets.bottom += StatusBarView.height + 5 case .top: insets.top += toolbarHeight case .bottom: - insets.bottom += StatusBarView.height + insets.bottom += StatusBarView.height + 5 default: return } diff --git a/CodeEdit/Features/SplitView/SplitView.swift b/CodeEdit/Features/SplitView/SplitView.swift index f6ef4c2d63..68b36e017e 100644 --- a/CodeEdit/Features/SplitView/SplitView.swift +++ b/CodeEdit/Features/SplitView/SplitView.swift @@ -8,7 +8,7 @@ import SwiftUI // swiftlint:disable identifier_name -struct Helper: _VariadicView_MultiViewRoot { +struct Helper: _VariadicView_UnaryViewRoot { var _body: (_VariadicView.Children) -> Result func body(children: _VariadicView.Children) -> some View { @@ -23,13 +23,102 @@ extension View { } struct SplitView: View { - var axis: Axis + var content: Content - @ViewBuilder var content: Content + @State var viewController: SplitViewController + + init(axis: Axis, @ViewBuilder content: () -> Content) { + self.content = content() + let vc = SplitViewController(axis: axis) + self._viewController = .init(wrappedValue: vc) + } var body: some View { - content.variadic { children in - EditorSplitView(axis: axis, children: children) + VStack { + content.variadic { children in + EditorSplitView(children: children, viewController: viewController) + } + } + ._trait(SplitViewControllerLayoutValueKey.self, viewController) + } +} + +struct SplitViewReader: View { + + @ViewBuilder var content: (SplitViewProxy) -> Content + + @State private var viewController: SplitViewController? + + private var proxy: SplitViewProxy { + .init { + viewController } } + + var body: some View { + content(proxy) + .variadic { children in + ForEach(children, id: \.id) { child in + child + .task { + if let vc = child[SplitViewControllerLayoutValueKey.self] { + viewController = vc + } + } + } + } + } +} + +struct SplitViewProxy { + private var viewController: () -> SplitViewController? + + fileprivate init(viewController: @escaping () -> SplitViewController?) { + self.viewController = viewController + } + + func setPosition(of index: Int, position: CGFloat) { + viewController()?.splitView.setPosition(position, ofDividerAt: index) + } + + func collapseView(with id: AnyHashable, _ enabled: Bool) { + viewController()?.collapse(for: id, enabled: enabled) + } +} + +struct SplitViewControllerLayoutValueKey: _ViewTraitKey { + static var defaultValue: SplitViewController? +} + +struct SplitViewItemCollapsedViewTraitKey: _ViewTraitKey { + static var defaultValue: Binding = .constant(false) +} + +struct SplitViewItemCanCollapseViewTraitKey: _ViewTraitKey { + static var defaultValue: Bool = false +} + +struct SplitViewItemMinimumHeightViewTraitKey: _ViewTraitKey { + static var defaultValue: Bool = false +} + +struct SplitViewItemMaximumHeightViewTraitKey: _ViewTraitKey { + static var defaultValue: Bool = false +} + +extension View { + func collapsed(_ value: Binding) -> some View { + self + // Use get/set instead of binding directly, so a view update will be triggered if the binding changes. + ._trait(SplitViewItemCollapsedViewTraitKey.self, .init { + value.wrappedValue + } set: { + value.wrappedValue = $0 + }) + } + + func collapsable() -> some View { + self + ._trait(SplitViewItemCanCollapseViewTraitKey.self, true) + } } diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift index bf0cf3e613..bc7f9e6a60 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift @@ -11,7 +11,10 @@ internal struct StatusBarToggleDrawerButton: View { @EnvironmentObject private var model: StatusBarViewModel - init() { + @Binding var collapsed: Bool + + init(collapsed: Binding) { + self._collapsed = collapsed CommandManager.shared.addCommand( name: "Toggle Drawer", title: "Toggle Drawer", @@ -23,9 +26,7 @@ internal struct StatusBarToggleDrawerButton: View { func togglePanel() { withAnimation { model.isExpanded.toggle() - if model.isExpanded && model.currentHeight < 1 { - model.currentHeight = 300 - } + collapsed.toggle() } self.model.saveIsExpandedToState() } @@ -38,7 +39,7 @@ internal struct StatusBarToggleDrawerButton: View { Image(systemName: "rectangle.bottomthird.inset.filled") .imageScale(.medium) } - .tint(model.isExpanded ? .accentColor : .primary) + .tint(collapsed ? .primary : .accentColor) .keyboardShortcut("Y", modifiers: [.command, .shift]) .buttonStyle(.borderless) .onHover { isHovering($0) } diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index 5e33038495..c8a1f5024d 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -23,26 +23,25 @@ struct StatusBarView: View { @EnvironmentObject private var model: StatusBarViewModel + @ObservedObject + private var prefs: AppPreferencesModel = .shared + static let height = 29.0 - var body: some View { - VStack(spacing: 0) { - bar - if model.isExpanded { - StatusBarDrawer() - .transition(.move(edge: .bottom)) - } - } - .disabled(controlActive == .inactive) - // removes weird light gray bar above when in light mode - .padding(.top, -8) // (comment out to make it look normal in preview) - } + @Environment(\.colorScheme) + private var colorScheme + + var proxy: SplitViewProxy + + @Binding var collapsed: Bool + + static let statusbarID = "statusbarID" /// The actual status bar - private var bar: some View { - ZStack { - Rectangle() - .foregroundStyle(.bar) + var body: some View { + VStack { + Divider() +// Spacer() HStack(spacing: 15) { HStack(spacing: 5) { StatusBarBreakpointButton() @@ -50,47 +49,54 @@ struct StatusBarView: View { .frame(maxHeight: 12) .padding(.horizontal, 7) SegmentedControl($model.selectedTab, options: StatusBarTabType.allOptions) - .opacity(model.isExpanded ? 1 : 0) + .opacity(collapsed ? 0 : 1) } Spacer() StatusBarCursorLocationLabel() StatusBarIndentSelector() StatusBarEncodingSelector() StatusBarLineEndSelector() - StatusBarToggleDrawerButton() + StatusBarToggleDrawerButton(collapsed: $collapsed) } .padding(.horizontal, 10) +// Spacer() + Divider() } - .overlay(alignment: .top) { - PanelDivider() - } - .overlay(alignment: .bottom) { - if model.isExpanded { - PanelDivider() - } - } - .frame(height: Self.height) +// .overlay(alignment: .top) { +// PanelDivider() +// } +// .overlay(alignment: .bottom) { +// if model.isExpanded { +// PanelDivider() +// } +// } + .background(.bar) .gesture(dragGesture) .onHover { isHovering($0, isDragging: model.isDragging, cursor: .resizeUpDown) } + .disabled(controlActive == .inactive) + .frame(height: Self.height) + } /// A drag gesture to resize the drawer beneath the status bar private var dragGesture: some Gesture { - DragGesture() + + DragGesture(coordinateSpace: .global) .onChanged { value in model.isDragging = true - var newHeight = max(0, min(model.currentHeight - value.translation.height, 500)) - if newHeight-0.5 > model.currentHeight || newHeight+0.5 < model.currentHeight { - if newHeight < model.minHeight { // simulate the snapping/resistance after reaching minimal height - if newHeight > model.minHeight / 2 { - newHeight = model.minHeight - } else { - newHeight = 0 - } - } - model.currentHeight = newHeight - } - model.isExpanded = model.currentHeight < 1 ? false : true + proxy.setPosition(of: 0, position: value.location.y + Self.height / 2) +// var newHeight = max(0, min(model.currentHeight - value.translation.height, 500)) +// if newHeight-0.5 > model.currentHeight || newHeight+0.5 < model.currentHeight { +// if newHeight < model.minHeight { // simulate the snapping/resistance after reaching minimal height +// if newHeight > model.minHeight / 2 { +// newHeight = model.minHeight +// } else { +// newHeight = 0 +// } +// } +// model.currentHeight = newHeight +// } +// model.isExpanded = model.currentHeight < 1 ? false : true } .onEnded { _ in model.isDragging = false diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 151a0577e8..0568adf5df 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -41,22 +41,49 @@ struct WorkspaceView: View { @Environment(\.colorScheme) var colorScheme + @State var terminalCollapsed = false + var body: some View { ZStack { if workspace.workspaceClient != nil, let model = workspace.statusBarModel { - ZStack { - EditorView(tabgroup: workspace.tabs).id(UUID()) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) -// .safeAreaInset(edge: .top, spacing: 0) { -// VStack(spacing: 0) { -// TabBarView() -// TabBarBottomDivider() -// } -// } - .safeAreaInset(edge: .bottom) { - StatusBarView() + VStack { + SplitViewReader { proxy in + SplitView(axis: .vertical) { + + EditorView(tabgroup: workspace.tabs) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .safeAreaInset(edge: .bottom, spacing: 0) { + StatusBarView(proxy: proxy, collapsed: $terminalCollapsed) + } + +// if model.isExpanded { + TerminalEmulatorView(url: model.workspaceURL) + .background { + if colorScheme == .dark { + if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedLightTheme { + Color.white + } else { + EffectView(.underPageBackground) + } + } else { + if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedDarkTheme { + Color.black + } else { + EffectView(.contentBackground) + } + } + } + .id(StatusBarView.statusbarID) + .collapsable() + .collapsed($terminalCollapsed) + .frame(minHeight: 200, maxHeight: 400) +// } + } + + .edgesIgnoringSafeArea(.top) .environmentObject(model) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } } } else { EmptyView() From 6eaf8c58e31fb6db13d55d6ec2be9a32713969c2 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 1 Mar 2023 15:07:34 +0100 Subject: [PATCH 11/82] fixed some things regarding splitview Signed-off-by: Wouter01 --- CodeEdit/Features/Documents/WorkspaceDocument.swift | 9 +++++---- .../Features/SplitView/WorkspaceTabGroupView.swift | 11 +++++++++++ CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift | 9 +++++++++ CodeEdit/WorkspaceView.swift | 2 ++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index da5f560a92..e6ce24b277 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -24,14 +24,15 @@ import CodeEditKit @Published var tabs: TabGroup - var activeTab: TabGroupData + @Published var activeTab: TabGroupData override init() { - self.activeTab = .init() - self.tabs = .horizontal(.init(.horizontal, tabgroups: [.one(activeTab)])) + let tab = TabGroupData() + self.activeTab = tab + self.tabs = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) super.init() } - + var workspaceState: [String: Any] { get { let key = "workspaceState-\(self.fileURL?.absoluteString ?? "")" diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index 526d5fdcab..c923ae384a 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -10,6 +10,10 @@ import SwiftUI struct WorkspaceTabGroupView: View { @ObservedObject var tabgroup: TabGroupData + @EnvironmentObject var workspace: WorkspaceDocument + + @FocusState var isFocused + var body: some View { VStack { if let selected = tabgroup.selected { @@ -35,6 +39,7 @@ struct WorkspaceTabGroupView: View { VStack(spacing: 0) { TabBarView() .environmentObject(tabgroup) + .environment(\.controlActiveState, tabgroup == workspace.activeTab ? .key : .inactive) Divider() if let file = tabgroup.selected { @@ -54,5 +59,11 @@ struct WorkspaceTabGroupView: View { } .background(EffectView(.titlebar, blendingMode: .withinWindow, emphasized: false)) } + .focused($isFocused) + .onChange(of: isFocused) { focused in + if focused { + workspace.activeTab = tabgroup + } + } } } diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 37c131f90f..1a775b9af1 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -28,10 +28,19 @@ final class TabGroupData: ObservableObject { } } } + @Published var selected: WorkspaceClient.FileItem? + let uuid = UUID() + init(files: OrderedSet = [], selected: WorkspaceClient.FileItem? = nil) { self.files = files self.selected = selected ?? files.first } } + +extension TabGroupData: Equatable { + static func == (lhs: TabGroupData, rhs: TabGroupData) -> Bool { + lhs.uuid == rhs.uuid + } +} diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 0568adf5df..86b668b18a 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -55,6 +55,7 @@ struct WorkspaceView: View { .safeAreaInset(edge: .bottom, spacing: 0) { StatusBarView(proxy: proxy, collapsed: $terminalCollapsed) } + .layoutPriority(2) // if model.isExpanded { TerminalEmulatorView(url: model.workspaceURL) @@ -77,6 +78,7 @@ struct WorkspaceView: View { .collapsable() .collapsed($terminalCollapsed) .frame(minHeight: 200, maxHeight: 400) + .layoutPriority(1) // } } From 2c266bd7318e8d71d83a0943b2c154d6bc74ef69 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 1 Mar 2023 15:20:27 +0100 Subject: [PATCH 12/82] added filter bar to terminal view Signed-off-by: Wouter01 --- CodeEdit/WorkspaceView.swift | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 86b668b18a..1451083024 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -43,6 +43,9 @@ struct WorkspaceView: View { @State var terminalCollapsed = false + @State + private var searchText = "" + var body: some View { ZStack { if workspace.workspaceClient != nil, let model = workspace.statusBarModel { @@ -57,7 +60,7 @@ struct WorkspaceView: View { } .layoutPriority(2) -// if model.isExpanded { + VStack { TerminalEmulatorView(url: model.workspaceURL) .background { if colorScheme == .dark { @@ -74,14 +77,25 @@ struct WorkspaceView: View { } } } - .id(StatusBarView.statusbarID) - .collapsable() - .collapsed($terminalCollapsed) - .frame(minHeight: 200, maxHeight: 400) - .layoutPriority(1) -// } + HStack(alignment: .center, spacing: 10) { + FilterTextField(title: "Filter", text: $searchText) + .frame(maxWidth: 300) + Spacer() + StatusBarClearButton() + Divider() + StatusBarSplitTerminalButton() + StatusBarMaximizeButton() + } + .padding(10) + .frame(maxHeight: 29) + .background(.bar) + } + .id(StatusBarView.statusbarID) + .collapsable() + .collapsed($terminalCollapsed) + .frame(minHeight: 200, maxHeight: 400) + .layoutPriority(1) } - .edgesIgnoringSafeArea(.top) .environmentObject(model) .frame(maxWidth: .infinity, maxHeight: .infinity) From f23e8ea6eb863ef179687d1e7318498bd39df96c Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 1 Mar 2023 16:55:47 +0100 Subject: [PATCH 13/82] Fixed small issues Signed-off-by: Wouter01 --- .../Features/SplitView/EditorSplitView.swift | 9 +++- .../StatusBarDrawer/StatusBarDrawer.swift | 52 +++++-------------- .../StatusBar/Views/StatusBarView.swift | 27 ---------- CodeEdit/WorkspaceView.swift | 45 +++------------- 4 files changed, 27 insertions(+), 106 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorSplitView.swift b/CodeEdit/Features/SplitView/EditorSplitView.swift index 4730be80d1..a7ac60c623 100644 --- a/CodeEdit/Features/SplitView/EditorSplitView.swift +++ b/CodeEdit/Features/SplitView/EditorSplitView.swift @@ -24,6 +24,7 @@ class SplitViewItem: ObservableObject { self.item = NSSplitViewItem(viewController: NSHostingController(rootView: child)) self.collapsed = child[SplitViewItemCollapsedViewTraitKey.self] self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] + self.item.isCollapsed = self.collapsed.wrappedValue self.observers = createObservers() } @@ -51,13 +52,17 @@ struct EditorSplitView: NSViewControllerRepresentable { var viewController: SplitViewController func makeNSViewController(context: Context) -> SplitViewController { + viewController.items = children.map { SplitViewItem(child: $0) } return viewController } func updateNSViewController(_ controller: SplitViewController, context: Context) { - print("Update") - // Reorder viewcontrollers if needed and add new ones. + updateItems(controller: controller) + } + + func updateItems(controller: SplitViewController) { var hasChanged = false + // Reorder viewcontrollers if needed and add new ones. controller.items = children.map { child in let item: SplitViewItem if let foundItem = controller.items.first(where: { $0.id == child.id }) { diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift b/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift index d9a24b5e1d..d9c1a547eb 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift @@ -20,44 +20,24 @@ struct StatusBarDrawer: View { @State private var searchText = "" - var height: CGFloat { - if model.isMaximized { - return model.maxHeight - } - if model.isExpanded { - return model.currentHeight - } - return 0 - } - var body: some View { VStack(spacing: 0) { - GeometryReader { geometryProxy in - switch model.selectedTab { - case 0: - TerminalEmulatorView(url: model.workspaceURL) - .background { - if colorScheme == .dark { - if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedLightTheme { - Color.white - } else { - EffectView(.underPageBackground) - } - } else { - if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedDarkTheme { - Color.black - } else { - EffectView(.contentBackground) - } - } + TerminalEmulatorView(url: model.workspaceURL) + .background { + if colorScheme == .dark { + if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedLightTheme { + Color.white + } else { + EffectView(.underPageBackground) } - // When size changes, save new height to workspace state. - .onChange(of: geometryProxy.size.height) { _ in - model.saveHeightToState(height: geometryProxy.size.height) + } else { + if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedDarkTheme { + Color.black + } else { + EffectView(.contentBackground) } - default: Rectangle().foregroundColor(Color(nsColor: .textBackgroundColor)) + } } - } HStack(alignment: .center, spacing: 10) { FilterTextField(title: "Filter", text: $searchText) .frame(maxWidth: 300) @@ -71,10 +51,6 @@ struct StatusBarDrawer: View { .frame(maxHeight: 29) .background(.bar) } - .frame( - minHeight: 0, - idealHeight: height, - maxHeight: height - ) + } } diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index c8a1f5024d..47d4f4b638 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -41,7 +41,6 @@ struct StatusBarView: View { var body: some View { VStack { Divider() -// Spacer() HStack(spacing: 15) { HStack(spacing: 5) { StatusBarBreakpointButton() @@ -59,17 +58,8 @@ struct StatusBarView: View { StatusBarToggleDrawerButton(collapsed: $collapsed) } .padding(.horizontal, 10) -// Spacer() Divider() } -// .overlay(alignment: .top) { -// PanelDivider() -// } -// .overlay(alignment: .bottom) { -// if model.isExpanded { -// PanelDivider() -// } -// } .background(.bar) .gesture(dragGesture) .onHover { isHovering($0, isDragging: model.isDragging, cursor: .resizeUpDown) } @@ -80,26 +70,9 @@ struct StatusBarView: View { /// A drag gesture to resize the drawer beneath the status bar private var dragGesture: some Gesture { - DragGesture(coordinateSpace: .global) .onChanged { value in - model.isDragging = true proxy.setPosition(of: 0, position: value.location.y + Self.height / 2) -// var newHeight = max(0, min(model.currentHeight - value.translation.height, 500)) -// if newHeight-0.5 > model.currentHeight || newHeight+0.5 < model.currentHeight { -// if newHeight < model.minHeight { // simulate the snapping/resistance after reaching minimal height -// if newHeight > model.minHeight / 2 { -// newHeight = model.minHeight -// } else { -// newHeight = 0 -// } -// } -// model.currentHeight = newHeight -// } -// model.isExpanded = model.currentHeight < 1 ? false : true - } - .onEnded { _ in - model.isDragging = false } } } diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 1451083024..3abfc393f3 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -41,10 +41,7 @@ struct WorkspaceView: View { @Environment(\.colorScheme) var colorScheme - @State var terminalCollapsed = false - - @State - private var searchText = "" + @State var terminalCollapsed = true var body: some View { ZStack { @@ -60,41 +57,11 @@ struct WorkspaceView: View { } .layoutPriority(2) - VStack { - TerminalEmulatorView(url: model.workspaceURL) - .background { - if colorScheme == .dark { - if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedLightTheme { - Color.white - } else { - EffectView(.underPageBackground) - } - } else { - if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedDarkTheme { - Color.black - } else { - EffectView(.contentBackground) - } - } - } - HStack(alignment: .center, spacing: 10) { - FilterTextField(title: "Filter", text: $searchText) - .frame(maxWidth: 300) - Spacer() - StatusBarClearButton() - Divider() - StatusBarSplitTerminalButton() - StatusBarMaximizeButton() - } - .padding(10) - .frame(maxHeight: 29) - .background(.bar) - } - .id(StatusBarView.statusbarID) - .collapsable() - .collapsed($terminalCollapsed) - .frame(minHeight: 200, maxHeight: 400) - .layoutPriority(1) + StatusBarDrawer() + .collapsable() + .collapsed($terminalCollapsed) + .frame(minHeight: 200, maxHeight: 400) + } .edgesIgnoringSafeArea(.top) .environmentObject(model) From 4ae956c0a36bf188b0facc30cd0c01c8733a0dd1 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 1 Mar 2023 17:14:40 +0100 Subject: [PATCH 14/82] statusbarview height fixes Signed-off-by: Wouter01 --- .../NavigatorSidebar/NavigatorSidebarToolbarBottom.swift | 2 +- CodeEdit/Features/StatusBar/Views/StatusBarView.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CodeEdit/Features/NavigatorSidebar/NavigatorSidebarToolbarBottom.swift b/CodeEdit/Features/NavigatorSidebar/NavigatorSidebarToolbarBottom.swift index 3d743f6a61..adcf5b2eb2 100644 --- a/CodeEdit/Features/NavigatorSidebar/NavigatorSidebarToolbarBottom.swift +++ b/CodeEdit/Features/NavigatorSidebar/NavigatorSidebarToolbarBottom.swift @@ -20,7 +20,7 @@ struct NavigatorSidebarToolbarBottom: View { Spacer() sortButton } - .frame(height: 29, alignment: .center) + .frame(height: 29) .frame(maxWidth: .infinity) .padding(.horizontal, 4) .overlay(alignment: .top) { diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index 47d4f4b638..bda45cf9e9 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -39,7 +39,7 @@ struct StatusBarView: View { /// The actual status bar var body: some View { - VStack { + VStack(spacing: 4) { Divider() HStack(spacing: 15) { HStack(spacing: 5) { @@ -58,13 +58,13 @@ struct StatusBarView: View { StatusBarToggleDrawerButton(collapsed: $collapsed) } .padding(.horizontal, 10) - Divider() +// Divider() } + .frame(height: Self.height + 3) .background(.bar) .gesture(dragGesture) .onHover { isHovering($0, isDragging: model.isDragging, cursor: .resizeUpDown) } .disabled(controlActive == .inactive) - .frame(height: Self.height) } From 4fbb872ea300a3f2f9d8fd9b3ad95ffad8ab887f Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 1 Mar 2023 17:17:28 +0100 Subject: [PATCH 15/82] Removed excess background Signed-off-by: Wouter01 --- .../Model/AppPreferencesModel.swift | 1 - .../StatusBarDrawer/StatusBarDrawer.swift | 19 ++----------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/CodeEdit/Features/AppPreferences/Model/AppPreferencesModel.swift b/CodeEdit/Features/AppPreferences/Model/AppPreferencesModel.swift index 5ad5cd391c..7d705b2fe7 100644 --- a/CodeEdit/Features/AppPreferences/Model/AppPreferencesModel.swift +++ b/CodeEdit/Features/AppPreferences/Model/AppPreferencesModel.swift @@ -32,7 +32,6 @@ final class AppPreferencesModel: ObservableObject { var preferences: AppPreferences { didSet { try? savePreferences() - objectWillChange.send() } } diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift b/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift index d9c1a547eb..25fbc1c525 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift @@ -11,8 +11,8 @@ struct StatusBarDrawer: View { @EnvironmentObject private var model: StatusBarViewModel - @ObservedObject - private var prefs: AppPreferencesModel = .shared +// @ObservedObject +// private var prefs: AppPreferencesModel = .shared @Environment(\.colorScheme) private var colorScheme @@ -23,21 +23,6 @@ struct StatusBarDrawer: View { var body: some View { VStack(spacing: 0) { TerminalEmulatorView(url: model.workspaceURL) - .background { - if colorScheme == .dark { - if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedLightTheme { - Color.white - } else { - EffectView(.underPageBackground) - } - } else { - if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedDarkTheme { - Color.black - } else { - EffectView(.contentBackground) - } - } - } HStack(alignment: .center, spacing: 10) { FilterTextField(title: "Filter", text: $searchText) .frame(maxWidth: 300) From db08422fb4ce9638efbee40dd0ccf157f7b55f45 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 1 Mar 2023 17:30:11 +0100 Subject: [PATCH 16/82] Focus fixes Signed-off-by: Wouter01 --- .../StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift | 3 --- CodeEdit/Features/Tabs/Views/TabBarItemView.swift | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift b/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift index 25fbc1c525..11dfda0f6c 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift @@ -11,9 +11,6 @@ struct StatusBarDrawer: View { @EnvironmentObject private var model: StatusBarViewModel -// @ObservedObject -// private var prefs: AppPreferencesModel = .shared - @Environment(\.colorScheme) private var colorScheme diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index ea23b27dbd..406774203c 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -97,6 +97,7 @@ struct TabBarItemView: View { /// Switch the active tab to current tab. private func switchAction() { // Only set the `selectedId` when they are not equal to avoid performance issue for now. + workspace.activeTab = tabs if tabs.selected != item { tabs.selected = item // if workspace.selectionState.selectedId != item.tabID { From 5c81da86281564e4d61a681481d2ff6be34d37be Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 1 Mar 2023 17:39:01 +0100 Subject: [PATCH 17/82] size fixes for statusbar Signed-off-by: Wouter01 --- CodeEdit/Features/StatusBar/Views/StatusBarView.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index bda45cf9e9..7d7d800896 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -58,14 +58,13 @@ struct StatusBarView: View { StatusBarToggleDrawerButton(collapsed: $collapsed) } .padding(.horizontal, 10) -// Divider() + .padding(.bottom, 3) } - .frame(height: Self.height + 3) + .frame(height: Self.height) .background(.bar) .gesture(dragGesture) .onHover { isHovering($0, isDragging: model.isDragging, cursor: .resizeUpDown) } .disabled(controlActive == .inactive) - } /// A drag gesture to resize the drawer beneath the status bar From 76dad9270f7c0113ccad8ea07292b8240a7f0c5c Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 1 Mar 2023 17:56:44 +0100 Subject: [PATCH 18/82] small fix Signed-off-by: Wouter01 --- .../StatusBar/Views/StatusBarView.swift | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index 7d7d800896..d302771c53 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -60,10 +60,10 @@ struct StatusBarView: View { .padding(.horizontal, 10) .padding(.bottom, 3) } + .cursor(.resizeUpDown) .frame(height: Self.height) .background(.bar) .gesture(dragGesture) - .onHover { isHovering($0, isDragging: model.isDragging, cursor: .resizeUpDown) } .disabled(controlActive == .inactive) } @@ -75,3 +75,27 @@ struct StatusBarView: View { } } } + +extension View { + func cursor(_ cursor: NSCursor) -> some View { + // Using onContinuousHover instead of onHover, as the latter is less reliable. + onHover { + if $0 { + cursor.push() + } else { + cursor.pop() + } + } +// onContinuousHover { phase in +// print(phase) +// switch phase { +// case .active: +// cursor.push() +// case .ended: +// while NSCursor.current == cursor { +// cursor.pop() +// } +// } +// } + } +} From 38787f1cbdccdff537c13c22586b84a401d361d5 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 1 Mar 2023 19:36:51 +0100 Subject: [PATCH 19/82] Added ability to close splitview Signed-off-by: Wouter01 --- .../Documents/WorkspaceDocument.swift | 10 +++++++--- .../SplitView/WorkspaceTabGroupView.swift | 1 + .../Features/Tabs/TabGroup/TabGroup.swift | 14 ++++++++++++++ .../Features/Tabs/TabGroup/TabGroupData.swift | 19 +++++++++++++++---- .../TabGroup/WorkspaceSplitViewData.swift | 18 ++++++++++++++++++ CodeEdit/Features/Tabs/Views/TabBarView.swift | 19 +++++++++++++++++-- 6 files changed, 72 insertions(+), 9 deletions(-) diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index e6ce24b277..08c5a450e6 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -24,7 +24,11 @@ import CodeEditKit @Published var tabs: TabGroup - @Published var activeTab: TabGroupData + weak var activeTab: TabGroupData? { + didSet { + objectWillChange.send() + } + } override init() { let tab = TabGroupData() @@ -76,8 +80,8 @@ import CodeEditKit func openTab(item: WorkspaceClient.FileItem) { Task { await MainActor.run { - activeTab.files.append(item) - activeTab.selected = item + activeTab?.files.append(item) + activeTab?.selected = item do { try openFile(item: item) } catch { diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index c923ae384a..66a212f6fb 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -38,6 +38,7 @@ struct WorkspaceTabGroupView: View { .safeAreaInset(edge: .top, spacing: 0) { VStack(spacing: 0) { TabBarView() + .id("TabBarView" + tabgroup.id.uuidString) .environmentObject(tabgroup) .environment(\.controlActiveState, tabgroup == workspace.activeTab ? .key : .inactive) diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift index 2733f436a0..9592e93c85 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift @@ -22,4 +22,18 @@ enum TabGroup { } } } + + func findSomeTabGroup() -> TabGroupData? { + switch self { + case .one(let tabGroupData): + return tabGroupData + case .vertical(let data), .horizontal(let data): + for tabgroup in data.tabgroups { + if let result = tabgroup.findSomeTabGroup() { + return result + } + } + return nil + } + } } diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 1a775b9af1..257e0b2532 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -8,7 +8,7 @@ import Foundation import OrderedCollections -final class TabGroupData: ObservableObject { +final class TabGroupData: ObservableObject, Identifiable { @Published var files: OrderedSet = [] { didSet { let change = files.symmetricDifference(oldValue) @@ -31,16 +31,27 @@ final class TabGroupData: ObservableObject { @Published var selected: WorkspaceClient.FileItem? - let uuid = UUID() + let id = UUID() - init(files: OrderedSet = [], selected: WorkspaceClient.FileItem? = nil) { + weak var parent: WorkspaceSplitViewData? + + init(files: OrderedSet = [], selected: WorkspaceClient.FileItem? = nil, parent: WorkspaceSplitViewData? = nil) { self.files = files self.selected = selected ?? files.first + self.parent = parent + } + + func close() { + parent?.closeTabGroup(with: id) + } + + deinit { + print("DEINITING CLASS WITH FILES \(files)") } } extension TabGroupData: Equatable { static func == (lhs: TabGroupData, rhs: TabGroupData) -> Bool { - lhs.uuid == rhs.uuid + lhs.id == rhs.id } } diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift index f92f92d4c9..acb051b621 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift @@ -15,10 +15,17 @@ class WorkspaceSplitViewData: ObservableObject { init(_ axis: Axis, tabgroups: [TabGroup] = []) { self.tabgroups = tabgroups self.axis = axis + + tabgroups.forEach { + if case .one(let tabGroupData) = $0 { + tabGroupData.parent = self + } + } } // Splits the editor at a certain index into two separate editors. func split(_ direction: Edge, at index: Int, new tabgroup: TabGroupData) { + tabgroup.parent = self switch (axis, direction) { case (.horizontal, .trailing), (.vertical, .bottom): tabgroups.insert(.one(tabgroup), at: index+1) @@ -39,4 +46,15 @@ class WorkspaceSplitViewData: ObservableObject { tabgroups[index] = .horizontal(.init(.horizontal, tabgroups: [tabgroups[index], .one(tabgroup)])) } } + + func closeTabGroup(with id: TabGroupData.ID) { + tabgroups.removeAll { tabgroup in + if case .one(let tabGroupData) = tabgroup { + if tabGroupData.id == id { + return true + } + } + return false + } + } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 58a54f4b19..b4bcdf6c1b 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -27,8 +27,8 @@ struct TabBarView: View { private var activeState /// The workspace document. - // @EnvironmentObject - // private var workspace: WorkspaceFiles + @EnvironmentObject + private var workspace: WorkspaceDocument @EnvironmentObject private var tabs: TabGroupData @@ -415,6 +415,21 @@ struct TabBarView: View { private var leadingAccessories: some View { HStack(spacing: 2) { + TabBarAccessoryIcon( + icon: .init(systemName: "multiply"), + action: { + tabs.close() + if workspace.activeTab == tabs { + workspace.activeTab = workspace.tabs.findSomeTabGroup() + } + } + ) + .foregroundColor(.secondary) + .buttonStyle(.plain) + .help("Close Tab Group") + Divider() + .frame(height: 10) + .padding(.horizontal, 4) TabBarAccessoryIcon( icon: .init(systemName: "chevron.left"), action: {} // TODO: Implement From f273443d72761d451696d12f1f8bad1a93aabc20 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 3 Mar 2023 00:57:53 +0100 Subject: [PATCH 20/82] Fixed some logic regarding closing splitviews Signed-off-by: Wouter01 --- .../Documents/WorkspaceDocument.swift | 12 ++++--- .../Features/Tabs/TabGroup/TabGroup.swift | 8 +++-- .../Features/Tabs/TabGroup/TabGroupData.swift | 6 +++- CodeEdit/Features/Tabs/Views/TabBarView.swift | 32 +++++++++++-------- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 08c5a450e6..f79e3b4b96 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -10,6 +10,7 @@ import AppKit import SwiftUI import Combine import CodeEditKit +import OrderedCollections // swiftlint:disable type_body_length // swiftlint:disable file_length @@ -24,15 +25,18 @@ import CodeEditKit @Published var tabs: TabGroup - weak var activeTab: TabGroupData? { + @Published var activeTab: TabGroupData { didSet { - objectWillChange.send() + activeTabHistory.updateOrInsert(oldValue, at: 0) } } + var activeTabHistory: OrderedSet = [] + override init() { let tab = TabGroupData() self.activeTab = tab + self.activeTabHistory.append(tab) self.tabs = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) super.init() } @@ -80,8 +84,8 @@ import CodeEditKit func openTab(item: WorkspaceClient.FileItem) { Task { await MainActor.run { - activeTab?.files.append(item) - activeTab?.selected = item + activeTab.files.append(item) + activeTab.selected = item do { try openFile(item: item) } catch { diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift index 9592e93c85..9a9891adc5 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift @@ -23,17 +23,19 @@ enum TabGroup { } } - func findSomeTabGroup() -> TabGroupData? { + func findSomeTabGroup(except: TabGroupData? = nil) -> TabGroupData? { switch self { - case .one(let tabGroupData): + case .one(let tabGroupData) where tabGroupData != except: return tabGroupData case .vertical(let data), .horizontal(let data): for tabgroup in data.tabgroups { - if let result = tabgroup.findSomeTabGroup() { + if let result = tabgroup.findSomeTabGroup(except: except), result != except { return result } } return nil + default: + return nil } } } diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 257e0b2532..1c34172cb4 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -50,8 +50,12 @@ final class TabGroupData: ObservableObject, Identifiable { } } -extension TabGroupData: Equatable { +extension TabGroupData: Equatable, Hashable { static func == (lhs: TabGroupData, rhs: TabGroupData) -> Bool { lhs.id == rhs.id } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index b4bcdf6c1b..1df7c4b379 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -415,21 +415,25 @@ struct TabBarView: View { private var leadingAccessories: some View { HStack(spacing: 2) { - TabBarAccessoryIcon( - icon: .init(systemName: "multiply"), - action: { - tabs.close() - if workspace.activeTab == tabs { - workspace.activeTab = workspace.tabs.findSomeTabGroup() + if workspace.tabs.findSomeTabGroup(except: tabs) != nil { + TabBarAccessoryIcon( + icon: .init(systemName: "multiply"), + action: { + tabs.close() + if workspace.activeTab == tabs { + workspace.activeTab = workspace.activeTabHistory.removeFirst() + } } - } - ) - .foregroundColor(.secondary) - .buttonStyle(.plain) - .help("Close Tab Group") - Divider() - .frame(height: 10) - .padding(.horizontal, 4) + ) + .foregroundColor(.secondary) + .buttonStyle(.plain) + .help("Close Tab Group") + + Divider() + .frame(height: 10) + .padding(.horizontal, 4) + } + TabBarAccessoryIcon( icon: .init(systemName: "chevron.left"), action: {} // TODO: Implement From 5d4266e2e3b6eea6618e957d2fb6849b9778a5fb Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 3 Mar 2023 01:41:08 +0100 Subject: [PATCH 21/82] Refactored tabs management Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 4 +++ .../Documents/WorkspaceDocument.swift | 22 ++------------- .../Features/SplitView/TabManagement.swift | 28 +++++++++++++++++++ .../SplitView/WorkspaceTabGroupView.swift | 6 ++-- .../Features/Tabs/Views/TabBarItemView.swift | 19 ++++--------- CodeEdit/Features/Tabs/Views/TabBarView.swift | 8 ++++-- CodeEdit/WorkspaceView.swift | 4 ++- 7 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 CodeEdit/Features/SplitView/TabManagement.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index f570478744..1c9805b9fa 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -325,6 +325,7 @@ 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; }; 6C7256D729A3D7D000C2D3E0 /* EditorSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */; }; 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */; }; + 6C91D57229B176FF0059A90D /* TabManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C91D57129B176FF0059A90D /* TabManagement.swift */; }; 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */; }; 6C97EBCF297876E500302F95 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCE297876E500302F95 /* AboutWindowController.swift */; }; 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */; }; @@ -717,6 +718,7 @@ 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ContentInsets.swift"; sourceTree = ""; }; 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorSplitView.swift; sourceTree = ""; }; + 6C91D57129B176FF0059A90D /* TabManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManagement.swift; sourceTree = ""; }; 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; 6C97EBCE297876E500302F95 /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Caption3.swift"; sourceTree = ""; }; @@ -2202,6 +2204,7 @@ 6C147C4829A32A080089B630 /* EditorView.swift */, 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */, 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */, + 6C91D57129B176FF0059A90D /* TabManagement.swift */, ); path = SplitView; sourceTree = ""; @@ -2651,6 +2654,7 @@ 5878DAA7291AE76700DD95A3 /* QuickOpenViewModel.swift in Sources */, 58F2EAED292FB2B0004A9BDE /* IgnoredFileView.swift in Sources */, 587B9E6529301D8F00AC7927 /* GitLabGroupAccess.swift in Sources */, + 6C91D57229B176FF0059A90D /* TabManagement.swift in Sources */, 58D01C9B293167DC00C5B6B4 /* CodeEditKeychainConstants.swift in Sources */, 58F2EB0C292FB2B0004A9BDE /* SourceControlAccounts.swift in Sources */, 20EBB50F280C389300F3A5DA /* FileInspectorModel.swift in Sources */, diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index f79e3b4b96..593fadfc78 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -23,23 +23,7 @@ import OrderedCollections @Published var selectionState: WorkspaceSelectionState = .init() @Published var fileItems: [WorkspaceClient.FileItem] = [] - @Published var tabs: TabGroup - - @Published var activeTab: TabGroupData { - didSet { - activeTabHistory.updateOrInsert(oldValue, at: 0) - } - } - - var activeTabHistory: OrderedSet = [] - - override init() { - let tab = TabGroupData() - self.activeTab = tab - self.activeTabHistory.append(tab) - self.tabs = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) - super.init() - } + var tabManager = TabManager() var workspaceState: [String: Any] { get { @@ -84,8 +68,8 @@ import OrderedCollections func openTab(item: WorkspaceClient.FileItem) { Task { await MainActor.run { - activeTab.files.append(item) - activeTab.selected = item + tabManager.activeTab.files.append(item) + tabManager.activeTab.selected = item do { try openFile(item: item) } catch { diff --git a/CodeEdit/Features/SplitView/TabManagement.swift b/CodeEdit/Features/SplitView/TabManagement.swift new file mode 100644 index 0000000000..7d052d8123 --- /dev/null +++ b/CodeEdit/Features/SplitView/TabManagement.swift @@ -0,0 +1,28 @@ +// +// TabManager.swift +// CodeEdit +// +// Created by Wouter Hennen on 03/03/2023. +// + +import Foundation +import OrderedCollections + +class TabManager: ObservableObject { + @Published var tabs: TabGroup + + @Published var activeTab: TabGroupData { + didSet { + activeTabHistory.updateOrInsert(oldValue, at: 0) + } + } + + var activeTabHistory: OrderedSet = [] + + init() { + let tab = TabGroupData() + self.activeTab = tab + self.activeTabHistory.append(tab) + self.tabs = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) + } +} diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index 66a212f6fb..815ee3700d 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -10,7 +10,7 @@ import SwiftUI struct WorkspaceTabGroupView: View { @ObservedObject var tabgroup: TabGroupData - @EnvironmentObject var workspace: WorkspaceDocument + @EnvironmentObject var tabManager: TabManager @FocusState var isFocused @@ -40,7 +40,7 @@ struct WorkspaceTabGroupView: View { TabBarView() .id("TabBarView" + tabgroup.id.uuidString) .environmentObject(tabgroup) - .environment(\.controlActiveState, tabgroup == workspace.activeTab ? .key : .inactive) + .environment(\.controlActiveState, tabgroup == tabManager.activeTab ? .key : .inactive) Divider() if let file = tabgroup.selected { @@ -63,7 +63,7 @@ struct WorkspaceTabGroupView: View { .focused($isFocused) .onChange(of: isFocused) { focused in if focused { - workspace.activeTab = tabgroup + tabManager.activeTab = tabgroup } } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index 406774203c..fefed9bc6a 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -20,7 +20,7 @@ struct TabBarItemView: View { @Environment(\.isFullscreen) private var isFullscreen - @EnvironmentObject var workspace: WorkspaceDocument + @EnvironmentObject var tabManager: TabManager /// User preferences. @StateObject @@ -97,7 +97,7 @@ struct TabBarItemView: View { /// Switch the active tab to current tab. private func switchAction() { // Only set the `selectedId` when they are not equal to avoid performance issue for now. - workspace.activeTab = tabs + tabManager.activeTab = tabs if tabs.selected != item { tabs.selected = item // if workspace.selectionState.selectedId != item.tabID { @@ -327,17 +327,10 @@ struct TabBarItemView: View { ) ) .onAppear { - if (isTemporary && workspace.selectionState.previousTemporaryTab == nil) - || !(isTemporary && workspace.selectionState.previousTemporaryTab != item.tabID) { - withAnimation( - .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) - ) { - isAppeared = true - } - } else { - withAnimation(.linear(duration: 0.0)) { - isAppeared = true - } + withAnimation( + .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) + ) { + isAppeared = true } } .id(item.id) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 1df7c4b379..00d62f6ca6 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -30,6 +30,8 @@ struct TabBarView: View { @EnvironmentObject private var workspace: WorkspaceDocument + @EnvironmentObject private var tabManager: TabManager + @EnvironmentObject private var tabs: TabGroupData @@ -415,13 +417,13 @@ struct TabBarView: View { private var leadingAccessories: some View { HStack(spacing: 2) { - if workspace.tabs.findSomeTabGroup(except: tabs) != nil { + if tabManager.tabs.findSomeTabGroup(except: tabs) != nil { TabBarAccessoryIcon( icon: .init(systemName: "multiply"), action: { tabs.close() - if workspace.activeTab == tabs { - workspace.activeTab = workspace.activeTabHistory.removeFirst() + if tabManager.activeTab == tabs { + tabManager.activeTab = tabManager.activeTabHistory.removeFirst() } } ) diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 3abfc393f3..3ee8abfc79 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -19,6 +19,8 @@ struct WorkspaceView: View { @ObservedObject var workspace: WorkspaceDocument + @EnvironmentObject private var tabManager: TabManager + @StateObject private var prefs: AppPreferencesModel = .shared @@ -50,7 +52,7 @@ struct WorkspaceView: View { SplitViewReader { proxy in SplitView(axis: .vertical) { - EditorView(tabgroup: workspace.tabs) + EditorView(tabgroup: tabManager.tabs) .frame(maxWidth: .infinity, maxHeight: .infinity) .safeAreaInset(edge: .bottom, spacing: 0) { StatusBarView(proxy: proxy, collapsed: $terminalCollapsed) From f7c700fe84b84e65cc20178cb164bed10029066c Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 3 Mar 2023 01:52:39 +0100 Subject: [PATCH 22/82] removed unused function Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 8 +++---- .../Documents/WorkspaceDocument.swift | 23 ------------------- .../{TabManagement.swift => TabManager.swift} | 0 3 files changed, 4 insertions(+), 27 deletions(-) rename CodeEdit/Features/SplitView/{TabManagement.swift => TabManager.swift} (100%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 1c9805b9fa..b532bb28e1 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -325,7 +325,7 @@ 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; }; 6C7256D729A3D7D000C2D3E0 /* EditorSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */; }; 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */; }; - 6C91D57229B176FF0059A90D /* TabManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C91D57129B176FF0059A90D /* TabManagement.swift */; }; + 6C91D57229B176FF0059A90D /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C91D57129B176FF0059A90D /* TabManager.swift */; }; 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */; }; 6C97EBCF297876E500302F95 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCE297876E500302F95 /* AboutWindowController.swift */; }; 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */; }; @@ -718,7 +718,7 @@ 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ContentInsets.swift"; sourceTree = ""; }; 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorSplitView.swift; sourceTree = ""; }; - 6C91D57129B176FF0059A90D /* TabManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManagement.swift; sourceTree = ""; }; + 6C91D57129B176FF0059A90D /* TabManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManager.swift; sourceTree = ""; }; 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; 6C97EBCE297876E500302F95 /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Caption3.swift"; sourceTree = ""; }; @@ -2204,7 +2204,7 @@ 6C147C4829A32A080089B630 /* EditorView.swift */, 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */, 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */, - 6C91D57129B176FF0059A90D /* TabManagement.swift */, + 6C91D57129B176FF0059A90D /* TabManager.swift */, ); path = SplitView; sourceTree = ""; @@ -2654,7 +2654,7 @@ 5878DAA7291AE76700DD95A3 /* QuickOpenViewModel.swift in Sources */, 58F2EAED292FB2B0004A9BDE /* IgnoredFileView.swift in Sources */, 587B9E6529301D8F00AC7927 /* GitLabGroupAccess.swift in Sources */, - 6C91D57229B176FF0059A90D /* TabManagement.swift in Sources */, + 6C91D57229B176FF0059A90D /* TabManager.swift in Sources */, 58D01C9B293167DC00C5B6B4 /* CodeEditKeychainConstants.swift in Sources */, 58F2EB0C292FB2B0004A9BDE /* SourceControlAccounts.swift in Sources */, 20EBB50F280C389300F3A5DA /* FileInspectorModel.swift in Sources */, diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 593fadfc78..70dc0a5658 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -79,29 +79,6 @@ import OrderedCollections } } - /// Updates the opened tabs and temporary tab. - /// - Parameter item: The item to use to update the tab state. - private func updateNewlyOpenedTabs(item: TabBarItemRepresentable) { - if !selectionState.openedTabs.contains(item.tabID) { - // If this isn't opened then we do the temp tab functionality - - // But, if there is already a temporary tab, close it first - if selectionState.temporaryTab != nil { - if let index = selectionState.openedTabs.firstIndex(of: selectionState.temporaryTab!) { - closeTemporaryTab() - selectionState.openedTabs[index] = item.tabID - } else { - selectionState.openedTabs.append(item.tabID) - } - } else { - selectionState.openedTabs.append(item.tabID) - } - - selectionState.previousTemporaryTab = selectionState.temporaryTab - selectionState.temporaryTab = item.tabID - } - } - private func openFile(item: WorkspaceClient.FileItem) throws { guard !selectionState.openFileItems.contains(item) else { return diff --git a/CodeEdit/Features/SplitView/TabManagement.swift b/CodeEdit/Features/SplitView/TabManager.swift similarity index 100% rename from CodeEdit/Features/SplitView/TabManagement.swift rename to CodeEdit/Features/SplitView/TabManager.swift From 4e5a9e20f8b191af013d2e93af119974b55e22e5 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Fri, 3 Mar 2023 02:19:10 +0100 Subject: [PATCH 23/82] Cleaned up code Signed-off-by: Wouter01 --- .../CodeEditWindowController.swift | 4 +- .../Documents/WorkspaceDocument.swift | 10 ++- .../ViewModels/StatusBarViewModel.swift | 5 +- CodeEdit/WorkspaceView.swift | 69 ++++++------------- 4 files changed, 33 insertions(+), 55 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index f66edd98f1..5fa9c7977f 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -80,7 +80,9 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { splitVC.addSplitViewItem(navigator) let workspaceView = WindowObserver(window: window!) { - WorkspaceView(workspace: workspace) + WorkspaceView() + .environmentObject(workspace) + .environmentObject(workspace.tabManager) } let mainContent = NSSplitViewItem( diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 70dc0a5658..78e2c030a5 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -36,12 +36,18 @@ import OrderedCollections } } - var statusBarModel: StatusBarViewModel? + var statusBarModel: StatusBarViewModel var searchState: SearchState? var quickOpenViewModel: QuickOpenViewModel? var commandsPaletteState: CommandPaletteViewModel? var listenerModel: WorkspaceNotificationModel = .init() + + override init() { + self.statusBarModel = StatusBarViewModel(workspace: self) + super.init() + } + private var cancellables = Set() private let openTabsStateName: String = "\(String(describing: WorkspaceDocument.self))-OpenTabs" private let activeTabStateName: String = "\(String(describing: WorkspaceDocument.self))-ActiveTab" @@ -343,7 +349,7 @@ import OrderedCollections self.searchState = .init(self) self.quickOpenViewModel = .init(fileURL: url) self.commandsPaletteState = .init() - self.statusBarModel = .init(workspace: self, workspaceURL: url) + self.statusBarModel.workspaceURL = url NotificationCenter.default.addObserver( self, diff --git a/CodeEdit/Features/StatusBar/ViewModels/StatusBarViewModel.swift b/CodeEdit/Features/StatusBar/ViewModels/StatusBarViewModel.swift index be0b08fa3c..8be40002d9 100644 --- a/CodeEdit/Features/StatusBar/ViewModels/StatusBarViewModel.swift +++ b/CodeEdit/Features/StatusBar/ViewModels/StatusBarViewModel.swift @@ -61,7 +61,7 @@ class StatusBarViewModel: ObservableObject { private(set) var workspace: WorkspaceDocument /// The base URL of the workspace - private(set) var workspaceURL: URL + var workspaceURL: URL? /// The maximum height of the drawer /// when isMaximized is true the height gets set to maxHeight @@ -75,9 +75,8 @@ class StatusBarViewModel: ObservableObject { /// Initialize with a GitClient /// - Parameter workspaceURL: the current workspace URL - init(workspace: WorkspaceDocument, workspaceURL: URL) { + init(workspace: WorkspaceDocument) { self.workspace = workspace - self.workspaceURL = workspaceURL var currentHeight = workspace.getFromWorkspaceState(key: statusBarDrawerHeightStateName) as? Double ?? self.standardHeight diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 3ee8abfc79..d734e807c5 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -9,14 +9,11 @@ import SwiftUI import AppKit struct WorkspaceView: View { - init(workspace: WorkspaceDocument) { - self.workspace = workspace - } let tabBarHeight = 28.0 private var path: String = "" - @ObservedObject + @EnvironmentObject var workspace: WorkspaceDocument @EnvironmentObject private var tabManager: TabManager @@ -32,59 +29,33 @@ struct WorkspaceView: View { @State private var showingAlert = false - @State - private var alertTitle = "" - - @State - private var alertMsg = "" - - @State - var showInspector = true - @Environment(\.colorScheme) var colorScheme @State var terminalCollapsed = true var body: some View { - ZStack { - if workspace.workspaceClient != nil, let model = workspace.statusBarModel { - VStack { - SplitViewReader { proxy in - SplitView(axis: .vertical) { - - EditorView(tabgroup: tabManager.tabs) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .safeAreaInset(edge: .bottom, spacing: 0) { - StatusBarView(proxy: proxy, collapsed: $terminalCollapsed) - } - .layoutPriority(2) - - StatusBarDrawer() - .collapsable() - .collapsed($terminalCollapsed) - .frame(minHeight: 200, maxHeight: 400) + if workspace.workspaceClient != nil { + VStack { + SplitViewReader { proxy in + SplitView(axis: .vertical) { + + EditorView(tabgroup: tabManager.tabs) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .safeAreaInset(edge: .bottom, spacing: 0) { + StatusBarView(proxy: proxy, collapsed: $terminalCollapsed) + } + .layoutPriority(2) + + StatusBarDrawer() + .collapsable() + .collapsed($terminalCollapsed) + .frame(minHeight: 200, maxHeight: 400) - } - .edgesIgnoringSafeArea(.top) - .environmentObject(model) - .frame(maxWidth: .infinity, maxHeight: .infinity) } + .edgesIgnoringSafeArea(.top) + .environmentObject(workspace.statusBarModel) + .frame(maxWidth: .infinity, maxHeight: .infinity) } - } else { - EmptyView() - } - } - .environmentObject(workspace) - .background(EffectView(.contentBackground)) - .alert(alertTitle, isPresented: $showingAlert, actions: { - Button( - action: { showingAlert = false }, - label: { Text("OK") } - ) - }, message: { Text(alertMsg) }) - .onChange(of: workspace.selectionState.selectedId) { newValue in - if newValue == nil { - window.subtitle = "" } } } From 8f44c079c62ddd21ca58a558603867d02aceccdc Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sat, 4 Mar 2023 02:02:38 +0100 Subject: [PATCH 24/82] Moved over code from workspacedocument Signed-off-by: Wouter01 --- .../CodeEditWindowController.swift | 9 +- .../Views/WorkspaceCodeFileView.swift | 8 +- .../Documents/WorkspaceDocument.swift | 197 +++++++++--------- .../FindNavigatorListViewController.swift | 4 +- .../NavigatorSidebarToolbarBottom.swift | 2 +- .../OutlineView/OutlineMenu.swift | 2 +- .../OutlineView/OutlineViewController.swift | 2 +- ...troller+OutlineTableViewCellDelegate.swift | 4 +- CodeEdit/Features/SplitView/TabManager.swift | 32 +++ .../ViewModels/StatusBarViewModel.swift | 30 +-- .../StatusBarDrawer/StatusBarDrawer.swift | 31 +-- .../StatusBarToggleDrawerButton.swift | 1 - .../Features/Tabs/TabGroup/TabGroupData.swift | 30 +++ .../Tabs/Views/TabBarContextMenu.swift | 18 +- .../WorkspaceClient/Model/FileItem.swift | 2 + 15 files changed, 203 insertions(+), 169 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 5fa9c7977f..813b144679 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -236,12 +236,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { } private func getSelectedCodeFile() -> CodeFileDocument? { - guard let id = workspace?.selectionState.selectedId else { return nil } - guard let item = workspace?.selectionState.openFileItems.first(where: { item in - item.tabID == id - }) else { return nil } - guard let file = workspace?.selectionState.openedCodeFiles[item] else { return nil } - return file + workspace?.tabManager.activeTab.selected?.fileDocument } @IBAction func saveDocument(_ sender: Any) { @@ -288,7 +283,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { let contentView = QuickOpenView( state: state, onClose: { panel.close() }, - openFile: workspace.openTab(item:) + openFile: workspace.tabManager.openTab(item:) ) panel.contentView = NSHostingView(rootView: contentView) window?.addChildWindow(panel, ordered: .above) diff --git a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift b/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift index de56d0a772..39a57f32b7 100644 --- a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift +++ b/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift @@ -9,13 +9,13 @@ import SwiftUI import UniformTypeIdentifiers struct WorkspaceCodeFileView: View { - @EnvironmentObject - private var workspace: WorkspaceDocument + + @EnvironmentObject private var tabManager: TabManager var file: WorkspaceClient.FileItem var document: CodeFileDocument? { - workspace.selectionState.openedCodeFiles[file] + file.fileDocument } @StateObject @@ -49,7 +49,7 @@ struct WorkspaceCodeFileView: View { for item: WorkspaceClient.FileItem ) -> some View { VStack(spacing: 0) { - BreadcrumbsView(file: item, tappedOpenFile: workspace.openTab(item:)) + BreadcrumbsView(file: item, tappedOpenFile: tabManager.openTab(item:)) Divider() if let url = otherFile.previewItemURL, diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 78e2c030a5..17e254745b 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -36,15 +36,13 @@ import OrderedCollections } } - var statusBarModel: StatusBarViewModel + var statusBarModel = StatusBarViewModel() var searchState: SearchState? var quickOpenViewModel: QuickOpenViewModel? var commandsPaletteState: CommandPaletteViewModel? var listenerModel: WorkspaceNotificationModel = .init() - override init() { - self.statusBarModel = StatusBarViewModel(workspace: self) super.init() } @@ -71,84 +69,78 @@ import OrderedCollections // MARK: Open Tabs /// Opens new tab /// - Parameter item: any item which can be represented as a tab - func openTab(item: WorkspaceClient.FileItem) { - Task { - await MainActor.run { - tabManager.activeTab.files.append(item) - tabManager.activeTab.selected = item - do { - try openFile(item: item) - } catch { - Swift.print(error) - } - } - } - } - - private func openFile(item: WorkspaceClient.FileItem) throws { - guard !selectionState.openFileItems.contains(item) else { - return - } - selectionState.openFileItems.append(item) - - let contentType = try item.url.resourceValues(forKeys: [.contentTypeKey]).contentType - let codeFile = try CodeFileDocument( - for: item.url, - withContentsOf: item.url, - ofType: contentType?.identifier ?? "" - ) - selectionState.openedCodeFiles[item] = codeFile - CodeEditDocumentController.shared.addDocument(codeFile) - Swift.print("Opening file for item: ", item.url) - } +// func openTab(item: WorkspaceClient.FileItem) { +// Task { +// await MainActor.run { +// tabManager.activeTab.files.append(item) +// tabManager.activeTab.selected = item +// do { +// try openFile(item: item) +// } catch { +// Swift.print(error) +// } +// } +// } +// } - private func openExtension(item: Plugin) { - if !selectionState.openedExtensions.contains(item) { - selectionState.openedExtensions.append(item) - } - } +// private func openFile(item: WorkspaceClient.FileItem) throws { +// guard !selectionState.openFileItems.contains(item) else { +// return +// } +// selectionState.openFileItems.append(item) +// +// let contentType = try item.url.resourceValues(forKeys: [.contentTypeKey]).contentType +// let codeFile = try CodeFileDocument( +// for: item.url, +// withContentsOf: item.url, +// ofType: contentType?.identifier ?? "" +// ) +// selectionState.openedCodeFiles[item] = codeFile +// CodeEditDocumentController.shared.addDocument(codeFile) +// Swift.print("Opening file for item: ", item.url) +// } // MARK: Close Tabs /// Closes single tab /// - Parameter id: tab bar item's identifier to be closed - func closeTab(item id: TabBarItemID) { - switch id { - case .codeEditor: - guard let item = selectionState.getItemByTab(id: id) as? WorkspaceClient.FileItem else { return } - closeFileTab(item: item) - case .extensionInstallation: - guard let item = selectionState.getItemByTab(id: id) as? Plugin else { return } - closeExtensionTab(item: item) - } - } +// func closeTab(item id: TabBarItemID) { +// switch id { +// case .codeEditor: +// guard let item = selectionState.getItemByTab(id: id) as? WorkspaceClient.FileItem else { return } +// closeFileTab(item: item) +// case .extensionInstallation: +// guard let item = selectionState.getItemByTab(id: id) as? Plugin else { return } +// closeExtensionTab(item: item) +// } +// } /// Closes collection of tab bar items /// - Parameter items: items to be closed - func closeTabs(items: Items) where Items: Collection, Items.Element == TabBarItemID { - // TODO: Could potentially be optimized - for item in items { - closeTab(item: item) - } - } +// func closeTabs(items: Items) where Items: Collection, Items.Element == TabBarItemID { +// // TODO: Could potentially be optimized +// for item in items { +// closeTab(item: item) +// } +// } /// Closes tabs according to predicator /// - Parameter predicate: predicator which returns whether tab should be closed based on its identifier - func closeTab(where predicate: (TabBarItemID) -> Bool) { - closeTabs(items: selectionState.openedTabs.filter(predicate)) - } +// func closeTab(where predicate: (TabBarItemID) -> Bool) { +// closeTabs(items: selectionState.openedTabs.filter(predicate)) +// } /// Closes tabs after specified identifier /// - Parameter id: identifier after which tabs will be closed - func closeTabs(after id: TabBarItemID) { - guard let startIdx = selectionState.openFileItems.firstIndex(where: { $0.tabID == id }) else { - assert(false, "Expected file item to be present in openFileItems") - return - } - - let range = selectionState.openedTabs[(startIdx+1)...] - closeTabs(items: range) - } +// func closeTabs(after id: TabBarItemID) { +// guard let startIdx = selectionState.openFileItems.firstIndex(where: { $0.tabID == id }) else { +// assert(false, "Expected file item to be present in openFileItems") +// return +// } +// +// let range = selectionState.openedTabs[(startIdx+1)...] +// closeTabs(items: range) +// } /// Switched the active tab to current tab /// - Parameter item: tab item that is now active. @@ -198,40 +190,40 @@ import OrderedCollections /// Closes an open tab, save text files only. /// Removes the tab item from `openedCodeFiles`, `openedExtensions`, and `openFileItems`. - private func closeFileTab(item: WorkspaceClient.FileItem) { - guard let file = selectionState.openedCodeFiles[item], - let openFileItemIndex = selectionState.openFileItems.firstIndex(of: item) - else { - return - } - if file.isDocumentEdited { - let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) - shouldClose.initialize(to: true) - defer { - _ = shouldClose.move() - shouldClose.deallocate() - } - file.canClose( - withDelegate: self, - shouldClose: #selector(document(_:shouldClose:contextInfo:)), - contextInfo: shouldClose - ) - guard shouldClose.pointee else { - return - } - } - selectionState.openedCodeFiles.removeValue(forKey: item) - selectionState.openFileItems.remove(at: openFileItemIndex) - removeTab(id: item.tabID) - - if openedTabsFromState { - var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] - if let index = openTabsInState.firstIndex(of: item.url.absoluteString) { - openTabsInState.remove(at: index) - self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) - } - } - } +// private func closeFileTab(item: WorkspaceClient.FileItem) { +// guard let file = selectionState.openedCodeFiles[item], +// let openFileItemIndex = selectionState.openFileItems.firstIndex(of: item) +// else { +// return +// } +// if file.isDocumentEdited { +// let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) +// shouldClose.initialize(to: true) +// defer { +// _ = shouldClose.move() +// shouldClose.deallocate() +// } +// file.canClose( +// withDelegate: self, +// shouldClose: #selector(document(_:shouldClose:contextInfo:)), +// contextInfo: shouldClose +// ) +// guard shouldClose.pointee else { +// return +// } +// } +// selectionState.openedCodeFiles.removeValue(forKey: item) +// selectionState.openFileItems.remove(at: openFileItemIndex) +// removeTab(id: item.tabID) +// +// if openedTabsFromState { +// var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] +// if let index = openTabsInState.firstIndex(of: item.url.absoluteString) { +// openTabsInState.remove(at: index) +// self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) +// } +// } +// } private func closeExtensionTab(item: Plugin) { guard let idx = selectionState.openedExtensions.firstIndex(of: item) else { return } @@ -323,7 +315,7 @@ import OrderedCollections let tabUrl = URL(string: openTab)! if FileManager.default.fileExists(atPath: tabUrl.path) { let item = WorkspaceClient.FileItem(url: tabUrl) - self.openTab(item: item) + self.tabManager.openTab(item: item) self.convertTemporaryTab() if activeTabInState == openTab { activeTabID = item.tabID @@ -349,7 +341,6 @@ import OrderedCollections self.searchState = .init(self) self.quickOpenViewModel = .init(fileURL: url) self.commandsPaletteState = .init() - self.statusBarModel.workspaceURL = url NotificationCenter.default.addObserver( self, @@ -490,7 +481,7 @@ import OrderedCollections /// `shouldClose` becomes false if the user selects cancel, otherwise true. /// - contextInfo: The additional info which will be set `shouldClose`. /// `contextInfo` must be `UnsafeMutablePointer`. - @objc private func document( + @objc func document( _ document: NSDocument, shouldClose: Bool, contextInfo: UnsafeMutableRawPointer diff --git a/CodeEdit/Features/NavigatorSidebar/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift b/CodeEdit/Features/NavigatorSidebar/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift index e942c40381..6668c7b4f6 100644 --- a/CodeEdit/Features/NavigatorSidebar/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift +++ b/CodeEdit/Features/NavigatorSidebar/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift @@ -204,13 +204,13 @@ extension FindNavigatorListViewController: NSOutlineViewDelegate { let selectedMatch = self.selectedItem as? SearchResultMatchModel if selectedItem == nil || selectedMatch != item { self.selectedItem = item - workspace.openTab(item: item.file) + workspace.tabManager.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.openTab(item: item.file) + workspace.tabManager.openTab(item: item.file) } } } diff --git a/CodeEdit/Features/NavigatorSidebar/NavigatorSidebarToolbarBottom.swift b/CodeEdit/Features/NavigatorSidebar/NavigatorSidebarToolbarBottom.swift index adcf5b2eb2..8c698c3a49 100644 --- a/CodeEdit/Features/NavigatorSidebar/NavigatorSidebarToolbarBottom.swift +++ b/CodeEdit/Features/NavigatorSidebar/NavigatorSidebarToolbarBottom.swift @@ -39,7 +39,7 @@ struct NavigatorSidebarToolbarBottom: View { guard let newFileItem = try? workspace.workspaceClient?.getFileItem(newFile) else { return } - workspace.openTab(item: newFileItem) + workspace.tabManager.openTab(item: newFileItem) } } diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineMenu.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineMenu.swift index f9031a2ff1..1bd682d820 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineMenu.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineMenu.swift @@ -182,7 +182,7 @@ final class OutlineMenu: NSMenu { @objc private func openInTab() { if let item = item { - workspace?.openTab(item: item) + workspace?.tabManager.openTab(item: item) } } diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift index cbab79a6ea..d55f745acf 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift @@ -275,7 +275,7 @@ extension OutlineViewController: NSOutlineViewDelegate { guard let item = outlineView.item(atRow: selectedIndex) as? Item else { return } if item.children == nil && shouldSendSelectionUpdate { - workspace?.openTab(item: item) + workspace?.tabManager.openTab(item: item) } } diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlintViewController+OutlineTableViewCellDelegate.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlintViewController+OutlineTableViewCellDelegate.swift index e15becf403..eebc6eb47c 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlintViewController+OutlineTableViewCellDelegate.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlintViewController+OutlineTableViewCellDelegate.swift @@ -12,11 +12,11 @@ import Foundation extension OutlineViewController: OutlineTableViewCellDelegate { func moveFile(file: Item, to destination: URL) { if !file.isFolder { - workspace?.closeTab(item: .codeEditor(file.id)) + workspace?.tabManager.tabs.closeAllTabs(of: file) } file.move(to: destination) if !file.isFolder { - workspace?.openTab(item: file) + workspace?.tabManager.openTab(item: file) } } diff --git a/CodeEdit/Features/SplitView/TabManager.swift b/CodeEdit/Features/SplitView/TabManager.swift index 7d052d8123..112ca5d00e 100644 --- a/CodeEdit/Features/SplitView/TabManager.swift +++ b/CodeEdit/Features/SplitView/TabManager.swift @@ -19,10 +19,42 @@ class TabManager: ObservableObject { var activeTabHistory: OrderedSet = [] + var fileDocuments: [WorkspaceClient.FileItem: CodeFileDocument] = [:] + init() { let tab = TabGroupData() self.activeTab = tab self.activeTabHistory.append(tab) self.tabs = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) } + + func openTab(item: WorkspaceClient.FileItem) { + Task { + await MainActor.run { + activeTab.files.append(item) + activeTab.selected = item + do { + try openFile(item: item) + } catch { + Swift.print(error) + } + } + } + } + + private func openFile(item: WorkspaceClient.FileItem) throws { + guard item.fileDocument == nil else { + return + } + + let contentType = try item.url.resourceValues(forKeys: [.contentTypeKey]).contentType + let codeFile = try CodeFileDocument( + for: item.url, + withContentsOf: item.url, + ofType: contentType?.identifier ?? "" + ) + item.fileDocument = codeFile + CodeEditDocumentController.shared.addDocument(codeFile) + Swift.print("Opening file for item: ", item.url) + } } diff --git a/CodeEdit/Features/StatusBar/ViewModels/StatusBarViewModel.swift b/CodeEdit/Features/StatusBar/ViewModels/StatusBarViewModel.swift index 8be40002d9..5f697314c8 100644 --- a/CodeEdit/Features/StatusBar/ViewModels/StatusBarViewModel.swift +++ b/CodeEdit/Features/StatusBar/ViewModels/StatusBarViewModel.swift @@ -58,11 +58,6 @@ class StatusBarViewModel: ObservableObject { /// Returns the font for status bar items to use private(set) var toolbarFont: Font = .system(size: 11) - private(set) var workspace: WorkspaceDocument - - /// The base URL of the workspace - var workspaceURL: URL? - /// The maximum height of the drawer /// when isMaximized is true the height gets set to maxHeight private(set) var maxHeight: Double = 5000 @@ -73,28 +68,7 @@ class StatusBarViewModel: ObservableObject { /// The minimum height of the drawe private(set) var minHeight: Double = 100 - /// Initialize with a GitClient - /// - Parameter workspaceURL: the current workspace URL - init(workspace: WorkspaceDocument) { - self.workspace = workspace - - var currentHeight = workspace.getFromWorkspaceState(key: statusBarDrawerHeightStateName) as? Double - ?? self.standardHeight - if currentHeight == 0 { - currentHeight = self.standardHeight - } - - self.isExpanded = workspace.getFromWorkspaceState(key: isStatusBarDrawerCollapsedStateName) as? Bool ?? false - if self.isExpanded { - self.currentHeight = currentHeight - } - } - - func saveIsExpandedToState() { - self.workspace.addToWorkspaceState(key: isStatusBarDrawerCollapsedStateName, value: self.isExpanded) - } - - func saveHeightToState(height: Double) { - self.workspace.addToWorkspaceState(key: statusBarDrawerHeightStateName, value: height) + init() { + } } diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift b/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift index 11dfda0f6c..c2add2fd03 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarDrawer/StatusBarDrawer.swift @@ -9,7 +9,7 @@ import SwiftUI struct StatusBarDrawer: View { @EnvironmentObject - private var model: StatusBarViewModel + private var workspace: WorkspaceDocument @Environment(\.colorScheme) private var colorScheme @@ -18,21 +18,22 @@ struct StatusBarDrawer: View { private var searchText = "" var body: some View { - VStack(spacing: 0) { - TerminalEmulatorView(url: model.workspaceURL) - HStack(alignment: .center, spacing: 10) { - FilterTextField(title: "Filter", text: $searchText) - .frame(maxWidth: 300) - Spacer() - StatusBarClearButton() - Divider() - StatusBarSplitTerminalButton() - StatusBarMaximizeButton() + if let url = workspace.workspaceClient?.folderURL() { + VStack(spacing: 0) { + TerminalEmulatorView(url: url) + HStack(alignment: .center, spacing: 10) { + FilterTextField(title: "Filter", text: $searchText) + .frame(maxWidth: 300) + Spacer() + StatusBarClearButton() + Divider() + StatusBarSplitTerminalButton() + StatusBarMaximizeButton() + } + .padding(10) + .frame(maxHeight: 29) + .background(.bar) } - .padding(10) - .frame(maxHeight: 29) - .background(.bar) } - } } diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift index bc7f9e6a60..3b71083b31 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift @@ -28,7 +28,6 @@ internal struct StatusBarToggleDrawerButton: View { model.isExpanded.toggle() collapsed.toggle() } - self.model.saveIsExpandedToState() } internal var body: some View { diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 1c34172cb4..c56737a23f 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -45,6 +45,36 @@ final class TabGroupData: ObservableObject, Identifiable { parent?.closeTabGroup(with: id) } + func closeTab(item: WorkspaceClient.FileItem) { + guard let file = item.fileDocument else { return } + + if file.isDocumentEdited { + let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) + shouldClose.initialize(to: true) + defer { + _ = shouldClose.move() + shouldClose.deallocate() + } + file.canClose( + withDelegate: self, + shouldClose: #selector(WorkspaceDocument.document(_:shouldClose:contextInfo:)), + contextInfo: shouldClose + ) + guard shouldClose.pointee else { + return + } + } + files.remove(item) + +// if openedTabsFromState { +// var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] +// if let index = openTabsInState.firstIndex(of: item.url.absoluteString) { +// openTabsInState.remove(at: index) +// self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) +// } +// } + } + deinit { print("DEINITING CLASS WITH FILES \(files)") } diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index 04170a20d8..9458ee6392 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -52,19 +52,27 @@ struct TabBarContextMenu: ViewModifier { } Button("Close Tab") { withAnimation { - workspace.closeTab(item: item.tabID) + tabs.closeTab(item: item) } } .keyboardShortcut("w", modifiers: [.command]) Button("Close Other Tabs") { withAnimation { - workspace.closeTab(where: { $0 != item.tabID }) + tabs.files.forEach { file in + if file != item { + tabs.closeTab(item: file) + } + } } } Button("Close Tabs to the Right") { withAnimation { - workspace.closeTabs(after: item.tabID) + if let index = tabs.files.firstIndex(of: item) { + tabs.files[index...].forEach { + tabs.closeTab(item: $0) + } + } } } // Disable this option when current tab is the last one. @@ -72,7 +80,9 @@ struct TabBarContextMenu: ViewModifier { Button("Close All") { withAnimation { - workspace.closeTabs(items: workspace.selectionState.openedTabs) + tabs.files.forEach { + tabs.closeTab(item: $0) + } } } diff --git a/CodeEdit/Utils/WorkspaceClient/Model/FileItem.swift b/CodeEdit/Utils/WorkspaceClient/Model/FileItem.swift index 8f8cb6a01d..00731289c9 100644 --- a/CodeEdit/Utils/WorkspaceClient/Model/FileItem.swift +++ b/CodeEdit/Utils/WorkspaceClient/Model/FileItem.swift @@ -168,6 +168,8 @@ extension WorkspaceClient { FileIcon.iconColor(fileType: fileType) } + var fileDocument: CodeFileDocument? + // MARK: Statics /// The default `FileManager` instance From 31f15a856f140214782888407af3a539a4c36c3b Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sat, 4 Mar 2023 02:19:30 +0100 Subject: [PATCH 25/82] Further cleaned up code Signed-off-by: Wouter01 --- .../Documents/WorkspaceDocument.swift | 210 +++++++++--------- .../Features/Tabs/TabGroup/TabGroupData.swift | 3 - 2 files changed, 104 insertions(+), 109 deletions(-) diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 17e254745b..fbdcb57843 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -144,49 +144,49 @@ import OrderedCollections /// Switched the active tab to current tab /// - Parameter item: tab item that is now active. - func switchedTab(item: TabBarItemRepresentable) { - selectionState.selectedId = item.tabID - guard let fileItem = item as? WorkspaceClient.FileItem else { return } - self.addToWorkspaceState(key: activeTabStateName, value: fileItem.url.absoluteString) - } +// func switchedTab(item: TabBarItemRepresentable) { +// selectionState.selectedId = item.tabID +// guard let fileItem = item as? WorkspaceClient.FileItem else { return } +// self.addToWorkspaceState(key: activeTabStateName, value: fileItem.url.absoluteString) +// } /// Tabs reordered /// - Parameter openedTabs: reordered tabs - func reorderedTabs(openedTabs: [TabBarItemID]) { - selectionState.openedTabs = openedTabs - - if openedTabsFromState { - var openTabsInState: [String] = [] - for openTabId in openedTabs { - guard let item = selectionState.getItemByTab(id: openTabId) as? WorkspaceClient.FileItem - else { continue } - openTabsInState.append(item.url.absoluteString) - } - self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) - } - } +// func reorderedTabs(openedTabs: [TabBarItemID]) { +// selectionState.openedTabs = openedTabs +// +// if openedTabsFromState { +// var openTabsInState: [String] = [] +// for openTabId in openedTabs { +// guard let item = selectionState.getItemByTab(id: openTabId) as? WorkspaceClient.FileItem +// else { continue } +// openTabsInState.append(item.url.absoluteString) +// } +// self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) +// } +// } /// Closes an open temporary tab, does not save the temporary tab's file. /// Removes the tab item from `openedCodeFiles`, `openedExtensions`, and `openFileItems`. - private func closeTemporaryTab() { - guard let id = selectionState.temporaryTab else { return } - - switch id { - case .codeEditor: - guard let item = selectionState.getItemByTab(id: id) - as? WorkspaceClient.FileItem else { return } - selectionState.openedCodeFiles.removeValue(forKey: item) - case .extensionInstallation: - guard let item = selectionState.getItemByTab(id: id) - as? Plugin else { return } - closeExtensionTab(item: item) - } - - guard let openFileItemIdx = selectionState - .openFileItems - .firstIndex(where: { $0.tabID == id }) else { return } - selectionState.openFileItems.remove(at: openFileItemIdx) - } +// private func closeTemporaryTab() { +// guard let id = selectionState.temporaryTab else { return } +// +// switch id { +// case .codeEditor: +// guard let item = selectionState.getItemByTab(id: id) +// as? WorkspaceClient.FileItem else { return } +// selectionState.openedCodeFiles.removeValue(forKey: item) +// case .extensionInstallation: +// guard let item = selectionState.getItemByTab(id: id) +// as? Plugin else { return } +// closeExtensionTab(item: item) +// } +// +// guard let openFileItemIdx = selectionState +// .openFileItems +// .firstIndex(where: { $0.tabID == id }) else { return } +// selectionState.openFileItems.remove(at: openFileItemIdx) +// } /// Closes an open tab, save text files only. /// Removes the tab item from `openedCodeFiles`, `openedExtensions`, and `openFileItems`. @@ -225,58 +225,58 @@ import OrderedCollections // } // } - private func closeExtensionTab(item: Plugin) { - guard let idx = selectionState.openedExtensions.firstIndex(of: item) else { return } - selectionState.openedExtensions.remove(at: idx) - - removeTab(id: item.tabID) - } +// private func closeExtensionTab(item: Plugin) { +// guard let idx = selectionState.openedExtensions.firstIndex(of: item) else { return } +// selectionState.openedExtensions.remove(at: idx) +// +// removeTab(id: item.tabID) +// } /// Makes the temporary tab permanent when a file save or edit happens. @objc func convertTemporaryTab() { - if selectionState.selectedId == selectionState.temporaryTab && - selectionState.temporaryTab != nil { - let item = selectionState.getItemByTab(id: selectionState.temporaryTab!) - selectionState.previousTemporaryTab = selectionState.temporaryTab - selectionState.temporaryTab = nil - - guard let file = item as? WorkspaceClient.FileItem else { return } - - if openedTabsFromState && item != nil { - var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] - if !openTabsInState.contains(file.url.absoluteString) { - openTabsInState.append(file.url.absoluteString) - self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) - } - } - } +// if selectionState.selectedId == selectionState.temporaryTab && +// selectionState.temporaryTab != nil { +// let item = selectionState.getItemByTab(id: selectionState.temporaryTab!) +// selectionState.previousTemporaryTab = selectionState.temporaryTab +// selectionState.temporaryTab = nil +// +// guard let file = item as? WorkspaceClient.FileItem else { return } +// +// if openedTabsFromState && item != nil { +// var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] +// if !openTabsInState.contains(file.url.absoluteString) { +// openTabsInState.append(file.url.absoluteString) +// self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) +// } +// } +// } } /// Removes the tab from `openedTabs`. /// - Parameter id: The id of `TabBarItemID` which will be removed. - private func removeTab(id: TabBarItemID) { - if id == selectionState.temporaryTab { - selectionState.previousTemporaryTab = selectionState.temporaryTab - selectionState.temporaryTab = nil - } - - guard let idx = selectionState.openedTabs.firstIndex(of: id) else { return } - let closedID = selectionState.openedTabs.remove(at: idx) - guard closedID == id else { return } - - if selectionState.openedTabs.isEmpty { - selectionState.selectedId = nil - } else if selectionState.selectedId == closedID { - // If the closed item is the selected one, then select another tab. - if idx == 0 { - selectionState.selectedId = selectionState.openedTabs.first - } else { - selectionState.selectedId = selectionState.openedTabs[idx - 1] - } - } else { - // If the closed item is not the selected one, then do nothing. - } - } +// private func removeTab(id: TabBarItemID) { +// if id == selectionState.temporaryTab { +// selectionState.previousTemporaryTab = selectionState.temporaryTab +// selectionState.temporaryTab = nil +// } +// +// guard let idx = selectionState.openedTabs.firstIndex(of: id) else { return } +// let closedID = selectionState.openedTabs.remove(at: idx) +// guard closedID == id else { return } +// +// if selectionState.openedTabs.isEmpty { +// selectionState.selectedId = nil +// } else if selectionState.selectedId == closedID { +// // If the closed item is the selected one, then select another tab. +// if idx == 0 { +// selectionState.selectedId = selectionState.openedTabs.first +// } else { +// selectionState.selectedId = selectionState.openedTabs[idx - 1] +// } +// } else { +// // If the closed item is not the selected one, then do nothing. +// } +// } // MARK: NSDocument @@ -308,24 +308,21 @@ import OrderedCollections windowController.window?.setFrameAutosaveName(self.fileURL?.absoluteString ?? "Untitled") self.addWindowController(windowController) - var activeTabID: TabBarItemID? - var activeTabInState = self.getFromWorkspaceState(key: activeTabStateName) as? String ?? "" - var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] - for openTab in openTabsInState { - let tabUrl = URL(string: openTab)! - if FileManager.default.fileExists(atPath: tabUrl.path) { - let item = WorkspaceClient.FileItem(url: tabUrl) - self.tabManager.openTab(item: item) - self.convertTemporaryTab() - if activeTabInState == openTab { - activeTabID = item.tabID - } - } - } - - if activeTabID != nil { - selectionState.selectedId = activeTabID - } + // TODO: Fix restoration +// var activeTabID: TabBarItemID? +// var activeTabInState = self.getFromWorkspaceState(key: activeTabStateName) as? String ?? "" +// var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] +// for openTab in openTabsInState { +// let tabUrl = URL(string: openTab)! +// if FileManager.default.fileExists(atPath: tabUrl.path) { +// let item = WorkspaceClient.FileItem(url: tabUrl) +// self.tabManager.openTab(item: item) +// self.convertTemporaryTab() +// if activeTabInState == openTab { +// activeTabID = item.tabID +// } +// } +// } self.openedTabsFromState = true } @@ -342,12 +339,13 @@ import OrderedCollections self.quickOpenViewModel = .init(fileURL: url) self.commandsPaletteState = .init() - NotificationCenter.default.addObserver( - self, - selector: #selector(convertTemporaryTab), - name: NSNotification.Name("CodeEditor.didBeginEditing"), - object: nil - ) + // TODO: Fix temporary tabs +// NotificationCenter.default.addObserver( +// self, +// selector: #selector(convertTemporaryTab), +// name: NSNotification.Name("CodeEditor.didBeginEditing"), +// object: nil +// ) } override func read(from url: URL, ofType typeName: String) throws { diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index c56737a23f..69eb30a017 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -75,9 +75,6 @@ final class TabGroupData: ObservableObject, Identifiable { // } } - deinit { - print("DEINITING CLASS WITH FILES \(files)") - } } extension TabGroupData: Equatable, Hashable { From e645c30c39fd80f8f60533c596433e8fa458fc9d Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sat, 4 Mar 2023 02:34:32 +0100 Subject: [PATCH 26/82] Fixed references to selectionState Signed-off-by: Wouter01 --- .../CodeEditWindowController.swift | 6 +++ .../InspectorSidebarView.swift | 38 +++++++++---------- .../OutlineView/OutlineView.swift | 3 ++ .../OutlineView/OutlineViewController.swift | 11 +++--- .../ProjectNavigatorView.swift | 4 +- .../Tabs/Views/TabBarContextMenu.swift | 2 +- CodeEdit/Features/Tabs/Views/TabBarView.swift | 2 +- 7 files changed, 37 insertions(+), 29 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 813b144679..0d182d0e3d 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -71,6 +71,9 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { let splitVC = CodeEditSplitViewController(workspace: workspace, feedbackPerformer: feedbackPerformer) let navigatorView = NavigatorSidebarView(workspace: workspace) + .environmentObject(workspace) + .environmentObject(workspace.tabManager) + let navigator = NSSplitViewItem( sidebarWithViewController: NSHostingController(rootView: navigatorView) ) @@ -92,6 +95,9 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { splitVC.addSplitViewItem(mainContent) let inspectorView = InspectorSidebarView(workspace: workspace) + .environmentObject(workspace) + .environmentObject(workspace.tabManager) + let inspector = NSSplitViewItem( viewController: NSHostingController(rootView: inspectorView) ) diff --git a/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift b/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift index 8ac356c360..612283ad97 100644 --- a/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift +++ b/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift @@ -12,6 +12,8 @@ struct InspectorSidebarView: View { @ObservedObject private var workspace: WorkspaceDocument + @EnvironmentObject private var tabManager: TabManager + @State private var selection: Int = 0 @@ -21,28 +23,22 @@ struct InspectorSidebarView: View { var body: some View { VStack { - if let item = workspace.selectionState.openFileItems.first(where: { file in - file.tabID == workspace.selectionState.selectedId - }) { - if let codeFile = workspace.selectionState.openedCodeFiles[item] { - switch selection { - case 0: - FileInspectorView( - workspaceURL: workspace.fileURL!, - fileURL: codeFile.fileURL!.path - ) - case 1: - HistoryInspectorView( - workspaceURL: workspace.fileURL!, - fileURL: codeFile.fileURL!.path - ) - case 2: - QuickHelpInspectorView().padding(5) - default: EmptyView() - } + if let path = tabManager.activeTab.selected?.fileDocument?.fileURL?.path(percentEncoded: false) { + switch selection { + case 0: + FileInspectorView( + workspaceURL: workspace.fileURL!, + fileURL: path + ) + case 1: + HistoryInspectorView( + workspaceURL: workspace.fileURL!, + fileURL: path + ) + case 2: + QuickHelpInspectorView().padding(5) + default: EmptyView() } - } else { - NoSelectionInspectorView() } } .frame( diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineView.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineView.swift index 37f75a036c..c055f83e1c 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineView.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineView.swift @@ -17,6 +17,9 @@ struct OutlineView: NSViewControllerRepresentable { @StateObject var prefs: AppPreferencesModel = .shared + // This is mainly just used to trigger a view update. + @Binding var selection: WorkspaceClient.FileItem? + typealias NSViewControllerType = OutlineViewController func makeNSViewController(context: Context) -> OutlineViewController { diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift index d55f745acf..3dcc06f010 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift @@ -90,12 +90,12 @@ final class OutlineViewController: NSViewController { /// /// Most importantly when the `id` changes from an external view. func updateSelection() { - guard let itemID = workspace?.selectionState.selectedId else { + guard let itemID = workspace?.tabManager.activeTab.selected?.id else { outlineView.deselectRow(outlineView.selectedRow) return } - select(by: itemID, from: content) + select(by: .codeEditor(itemID), from: content) } /// Expand or collapse the folder on double click @@ -110,9 +110,10 @@ final class OutlineViewController: NSViewController { outlineView.expandItem(item) } } else { - if workspace?.selectionState.temporaryTab == item.tabID { - workspace?.convertTemporaryTab() - } + // TODO: Fix temporary tab +// if workspace?.selectionState.temporaryTab == item.tabID { +// workspace?.convertTemporaryTab() +// } } } diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift index ed9ca184eb..bb2af391b5 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift @@ -16,7 +16,9 @@ import SwiftUI /// struct ProjectNavigatorView: View { + @EnvironmentObject var tabManager: TabManager + var body: some View { - OutlineView() + OutlineView(selection: $tabManager.activeTab.selected) } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index 9458ee6392..3f91b66486 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -76,7 +76,7 @@ struct TabBarContextMenu: ViewModifier { } } // Disable this option when current tab is the last one. - .disabled(workspace.selectionState.openedTabs.last?.id == item.tabID.id) + .disabled(tabs.files.last == item) Button("Close All") { withAnimation { diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 5781f8cb57..2537d81638 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -349,7 +349,7 @@ struct TabBarView: View { // On first tab appeared, jump to the corresponding position. scrollReader.scrollTo(tabs.selected) } - .onChange(of: workspace.selectionState.openedTabs) { _ in + .onChange(of: tabs.files) { _ in DispatchQueue.main.asyncAfter( deadline: .now() + .milliseconds( prefs.preferences.general.tabBarStyle == .native ? 150 : 200 From 07d5a338e0dc46742c1acec1c4dfcf8a72a22661 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sat, 4 Mar 2023 02:53:36 +0100 Subject: [PATCH 27/82] Removed references to selectionstate Signed-off-by: Wouter01 --- .../Documents/WorkspaceDocument.swift | 12 ++----- .../Features/Tabs/TabGroup/TabGroup.swift | 9 ++++++ CodeEdit/Features/Tabs/Views/TabBarView.swift | 32 +++++++++---------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index fbdcb57843..0f6bddf2c6 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -20,7 +20,7 @@ import OrderedCollections var extensionNavigatorData = ExtensionNavigatorData() @Published var sortFoldersOnTop: Bool = true - @Published var selectionState: WorkspaceSelectionState = .init() +// @Published var selectionState: WorkspaceSelectionState = .init() @Published var fileItems: [WorkspaceClient.FileItem] = [] var tabManager = TabManager() @@ -396,12 +396,6 @@ import OrderedCollections // MARK: Close Workspace override func close() { - selectionState.selectedId = nil - selectionState.openedCodeFiles.removeAll() - - if let url = self.fileURL { - ExtensionsManager.shared?.close(url: url) - } super.close() } @@ -440,7 +434,7 @@ import OrderedCollections return } // Save unsaved changes before closing - let editedCodeFiles = selectionState.openedCodeFiles.values.filter { $0.isDocumentEdited } + let editedCodeFiles = tabManager.tabs.gatherOpenFiles().compactMap(\.fileDocument).filter(\.isDocumentEdited) for editedCodeFile in editedCodeFiles { let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) shouldClose.initialize(to: true) @@ -465,7 +459,7 @@ import OrderedCollections implementation, to: (@convention(c)(Any, Selector, Any, Bool, UnsafeMutableRawPointer?) -> Void).self ) - let areAllOpenedCodeFilesClean = selectionState.openedCodeFiles.values.allSatisfy { !$0.isDocumentEdited } + let areAllOpenedCodeFilesClean = tabManager.tabs.gatherOpenFiles().compactMap(\.fileDocument).allSatisfy { !$0.isDocumentEdited } function(object, shouldCloseSelector, self, areAllOpenedCodeFilesClean, contextInfo) } diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift index 9a9891adc5..0e462818b5 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift @@ -38,4 +38,13 @@ enum TabGroup { return nil } } + + func gatherOpenFiles() -> Set { + switch self { + case .one(let tabGroupData): + return Set(tabGroupData.files) + case .vertical(let data), .horizontal(let data): + return data.tabgroups.map { $0.gatherOpenFiles() }.reduce(into: []) { $0.formUnion($1) } + } + } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 2537d81638..694e1a65a0 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -301,8 +301,8 @@ struct TabBarView: View { leadingAccessories // Tab bar items. GeometryReader { geometryProxy in - ScrollView(.horizontal, showsIndicators: false) { - ScrollViewReader { scrollReader in + ScrollViewReader { scrollReader in + ScrollView(.horizontal, showsIndicators: false) { HStack( alignment: .center, spacing: -1 // Negative spacing for overlapping the divider. @@ -355,8 +355,7 @@ struct TabBarView: View { prefs.preferences.general.tabBarStyle == .native ? 150 : 200 ) ) { - guard let selectedID = workspace.selectionState.selectedId else { return } - scrollReader.scrollTo(selectedID) + scrollReader.scrollTo(tabs.selected) } } // When selected tab is changed, scroll to it if possible. @@ -387,20 +386,21 @@ struct TabBarView: View { } .frame(height: TabBarView.height) } - } - // When there is no opened file, hide the scroll view, but keep the background. - .opacity( - tabs.files.isEmpty // TODO: Fix this && workspace.selectionState.temporaryTab == nil - ? 0.0 - : 1.0 - ) - // To fill up the parent space of tab bar. - .frame(maxWidth: .infinity) - .background { - if prefs.preferences.general.tabBarStyle == .native { - TabBarNativeInactiveBackground() + // When there is no opened file, hide the scroll view, but keep the background. + .opacity( + tabs.files.isEmpty // TODO: Fix this && workspace.selectionState.temporaryTab == nil + ? 0.0 + : 1.0 + ) + // To fill up the parent space of tab bar. + .frame(maxWidth: .infinity) + .background { + if prefs.preferences.general.tabBarStyle == .native { + TabBarNativeInactiveBackground() + } } } + } // Tab bar tools (e.g. split view). trailingAccessories From 699429a80b82397f8e3e941315b3f3b277c80d49 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sat, 4 Mar 2023 02:55:31 +0100 Subject: [PATCH 28/82] Removed commented out functions Signed-off-by: Wouter01 --- .../Documents/WorkspaceDocument.swift | 206 +----------------- 1 file changed, 1 insertion(+), 205 deletions(-) diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 0f6bddf2c6..1c9254455e 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -20,7 +20,7 @@ import OrderedCollections var extensionNavigatorData = ExtensionNavigatorData() @Published var sortFoldersOnTop: Bool = true -// @Published var selectionState: WorkspaceSelectionState = .init() + @Published var fileItems: [WorkspaceClient.FileItem] = [] var tabManager = TabManager() @@ -66,172 +66,6 @@ import OrderedCollections workspaceState.updateValue(value, forKey: key) } - // MARK: Open Tabs - /// Opens new tab - /// - Parameter item: any item which can be represented as a tab -// func openTab(item: WorkspaceClient.FileItem) { -// Task { -// await MainActor.run { -// tabManager.activeTab.files.append(item) -// tabManager.activeTab.selected = item -// do { -// try openFile(item: item) -// } catch { -// Swift.print(error) -// } -// } -// } -// } - -// private func openFile(item: WorkspaceClient.FileItem) throws { -// guard !selectionState.openFileItems.contains(item) else { -// return -// } -// selectionState.openFileItems.append(item) -// -// let contentType = try item.url.resourceValues(forKeys: [.contentTypeKey]).contentType -// let codeFile = try CodeFileDocument( -// for: item.url, -// withContentsOf: item.url, -// ofType: contentType?.identifier ?? "" -// ) -// selectionState.openedCodeFiles[item] = codeFile -// CodeEditDocumentController.shared.addDocument(codeFile) -// Swift.print("Opening file for item: ", item.url) -// } - - // MARK: Close Tabs - - /// Closes single tab - /// - Parameter id: tab bar item's identifier to be closed -// func closeTab(item id: TabBarItemID) { -// switch id { -// case .codeEditor: -// guard let item = selectionState.getItemByTab(id: id) as? WorkspaceClient.FileItem else { return } -// closeFileTab(item: item) -// case .extensionInstallation: -// guard let item = selectionState.getItemByTab(id: id) as? Plugin else { return } -// closeExtensionTab(item: item) -// } -// } - - /// Closes collection of tab bar items - /// - Parameter items: items to be closed -// func closeTabs(items: Items) where Items: Collection, Items.Element == TabBarItemID { -// // TODO: Could potentially be optimized -// for item in items { -// closeTab(item: item) -// } -// } - - /// Closes tabs according to predicator - /// - Parameter predicate: predicator which returns whether tab should be closed based on its identifier -// func closeTab(where predicate: (TabBarItemID) -> Bool) { -// closeTabs(items: selectionState.openedTabs.filter(predicate)) -// } - - /// Closes tabs after specified identifier - /// - Parameter id: identifier after which tabs will be closed -// func closeTabs(after id: TabBarItemID) { -// guard let startIdx = selectionState.openFileItems.firstIndex(where: { $0.tabID == id }) else { -// assert(false, "Expected file item to be present in openFileItems") -// return -// } -// -// let range = selectionState.openedTabs[(startIdx+1)...] -// closeTabs(items: range) -// } - - /// Switched the active tab to current tab - /// - Parameter item: tab item that is now active. -// func switchedTab(item: TabBarItemRepresentable) { -// selectionState.selectedId = item.tabID -// guard let fileItem = item as? WorkspaceClient.FileItem else { return } -// self.addToWorkspaceState(key: activeTabStateName, value: fileItem.url.absoluteString) -// } - - /// Tabs reordered - /// - Parameter openedTabs: reordered tabs -// func reorderedTabs(openedTabs: [TabBarItemID]) { -// selectionState.openedTabs = openedTabs -// -// if openedTabsFromState { -// var openTabsInState: [String] = [] -// for openTabId in openedTabs { -// guard let item = selectionState.getItemByTab(id: openTabId) as? WorkspaceClient.FileItem -// else { continue } -// openTabsInState.append(item.url.absoluteString) -// } -// self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) -// } -// } - - /// Closes an open temporary tab, does not save the temporary tab's file. - /// Removes the tab item from `openedCodeFiles`, `openedExtensions`, and `openFileItems`. -// private func closeTemporaryTab() { -// guard let id = selectionState.temporaryTab else { return } -// -// switch id { -// case .codeEditor: -// guard let item = selectionState.getItemByTab(id: id) -// as? WorkspaceClient.FileItem else { return } -// selectionState.openedCodeFiles.removeValue(forKey: item) -// case .extensionInstallation: -// guard let item = selectionState.getItemByTab(id: id) -// as? Plugin else { return } -// closeExtensionTab(item: item) -// } -// -// guard let openFileItemIdx = selectionState -// .openFileItems -// .firstIndex(where: { $0.tabID == id }) else { return } -// selectionState.openFileItems.remove(at: openFileItemIdx) -// } - - /// Closes an open tab, save text files only. - /// Removes the tab item from `openedCodeFiles`, `openedExtensions`, and `openFileItems`. -// private func closeFileTab(item: WorkspaceClient.FileItem) { -// guard let file = selectionState.openedCodeFiles[item], -// let openFileItemIndex = selectionState.openFileItems.firstIndex(of: item) -// else { -// return -// } -// if file.isDocumentEdited { -// let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) -// shouldClose.initialize(to: true) -// defer { -// _ = shouldClose.move() -// shouldClose.deallocate() -// } -// file.canClose( -// withDelegate: self, -// shouldClose: #selector(document(_:shouldClose:contextInfo:)), -// contextInfo: shouldClose -// ) -// guard shouldClose.pointee else { -// return -// } -// } -// selectionState.openedCodeFiles.removeValue(forKey: item) -// selectionState.openFileItems.remove(at: openFileItemIndex) -// removeTab(id: item.tabID) -// -// if openedTabsFromState { -// var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] -// if let index = openTabsInState.firstIndex(of: item.url.absoluteString) { -// openTabsInState.remove(at: index) -// self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) -// } -// } -// } - -// private func closeExtensionTab(item: Plugin) { -// guard let idx = selectionState.openedExtensions.firstIndex(of: item) else { return } -// selectionState.openedExtensions.remove(at: idx) -// -// removeTab(id: item.tabID) -// } - /// Makes the temporary tab permanent when a file save or edit happens. @objc func convertTemporaryTab() { // if selectionState.selectedId == selectionState.temporaryTab && @@ -252,32 +86,6 @@ import OrderedCollections // } } - /// Removes the tab from `openedTabs`. - /// - Parameter id: The id of `TabBarItemID` which will be removed. -// private func removeTab(id: TabBarItemID) { -// if id == selectionState.temporaryTab { -// selectionState.previousTemporaryTab = selectionState.temporaryTab -// selectionState.temporaryTab = nil -// } -// -// guard let idx = selectionState.openedTabs.firstIndex(of: id) else { return } -// let closedID = selectionState.openedTabs.remove(at: idx) -// guard closedID == id else { return } -// -// if selectionState.openedTabs.isEmpty { -// selectionState.selectedId = nil -// } else if selectionState.selectedId == closedID { -// // If the closed item is the selected one, then select another tab. -// if idx == 0 { -// selectionState.selectedId = selectionState.openedTabs.first -// } else { -// selectionState.selectedId = selectionState.openedTabs[idx - 1] -// } -// } else { -// // If the closed item is not the selected one, then do nothing. -// } -// } - // MARK: NSDocument private let ignoredFilesAndDirectory = [ @@ -377,18 +185,6 @@ import OrderedCollections } } .store(in: &cancellables) - - // initialize extensions - ExtensionManager.shared.loadExtensions { extensionID in - CodeEditAPI(extensionId: extensionID, workspace: self) - } -// do { -// try ExtensionsManager.shared?.load { extensionID in -// CodeEditAPI(extensionId: extensionID, workspace: self) -// } -// } catch let error { -// Swift.print(error) -// } } override func write(to url: URL, ofType typeName: String) throws {} From 1422d7fbe8271589dedfa857fdd6f01db40c3679 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sat, 4 Mar 2023 03:32:45 +0100 Subject: [PATCH 29/82] Bugfixes Signed-off-by: Wouter01 --- .../CodeEditWindowController.swift | 12 +++--- .../Views/WorkspaceCodeFileView.swift | 10 ++--- .../Documents/WorkspaceDocument.swift | 7 ++-- CodeEdit/Features/SplitView/TabManager.swift | 7 ++-- .../SplitView/WorkspaceTabGroupView.swift | 7 +--- .../Features/Tabs/TabGroup/TabGroupData.swift | 40 ++++++++++++++++++- 6 files changed, 58 insertions(+), 25 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 0d182d0e3d..f80c59e036 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -286,11 +286,13 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { } else { let panel = OverlayPanel() self.quickOpenPanel = panel - let contentView = QuickOpenView( - state: state, - onClose: { panel.close() }, - openFile: workspace.tabManager.openTab(item:) - ) + + let contentView = QuickOpenView(state: state) { + panel.close() + } openFile: { file in + workspace.tabManager.openTab(item: file) + } + panel.contentView = NSHostingView(rootView: contentView) window?.addChildWindow(panel, ordered: .above) panel.makeKeyAndOrderFront(self) diff --git a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift b/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift index 39a57f32b7..246e6b4d37 100644 --- a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift +++ b/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift @@ -12,18 +12,16 @@ struct WorkspaceCodeFileView: View { @EnvironmentObject private var tabManager: TabManager - var file: WorkspaceClient.FileItem + @EnvironmentObject private var tabgroup: TabGroupData - var document: CodeFileDocument? { - file.fileDocument - } + var file: WorkspaceClient.FileItem @StateObject private var prefs: AppPreferencesModel = .shared @ViewBuilder var codeView: some View { - if let document { + if let document = file.fileDocument { Group { switch document.typeOfFile { case .some(.text), .some(.data): @@ -49,8 +47,6 @@ struct WorkspaceCodeFileView: View { for item: WorkspaceClient.FileItem ) -> some View { VStack(spacing: 0) { - BreadcrumbsView(file: item, tappedOpenFile: tabManager.openTab(item:)) - Divider() if let url = otherFile.previewItemURL, let image = NSImage(contentsOf: url), diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 1c9254455e..30353e7028 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -10,10 +10,7 @@ import AppKit import SwiftUI import Combine import CodeEditKit -import OrderedCollections -// swiftlint:disable type_body_length -// swiftlint:disable file_length @objc(WorkspaceDocument) final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { var workspaceClient: WorkspaceClient? @@ -255,7 +252,9 @@ import OrderedCollections implementation, to: (@convention(c)(Any, Selector, Any, Bool, UnsafeMutableRawPointer?) -> Void).self ) - let areAllOpenedCodeFilesClean = tabManager.tabs.gatherOpenFiles().compactMap(\.fileDocument).allSatisfy { !$0.isDocumentEdited } + let areAllOpenedCodeFilesClean = tabManager.tabs.gatherOpenFiles() + .compactMap(\.fileDocument) + .allSatisfy { !$0.isDocumentEdited } function(object, shouldCloseSelector, self, areAllOpenedCodeFilesClean, contextInfo) } diff --git a/CodeEdit/Features/SplitView/TabManager.swift b/CodeEdit/Features/SplitView/TabManager.swift index 112ca5d00e..f94505ca91 100644 --- a/CodeEdit/Features/SplitView/TabManager.swift +++ b/CodeEdit/Features/SplitView/TabManager.swift @@ -28,11 +28,12 @@ class TabManager: ObservableObject { self.tabs = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) } - func openTab(item: WorkspaceClient.FileItem) { + func openTab(item: WorkspaceClient.FileItem, in tabgroup: TabGroupData? = nil) { Task { await MainActor.run { - activeTab.files.append(item) - activeTab.selected = item + let tabgroup = tabgroup ?? activeTab + tabgroup.files.append(item) + tabgroup.selected = item do { try openFile(item: item) } catch { diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index 815ee3700d..b09027c63c 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -46,13 +46,10 @@ struct WorkspaceTabGroupView: View { if let file = tabgroup.selected { BreadcrumbsView(file: file) { newFile in print("Opening \(newFile.fileName)") + let index = tabgroup.files.firstIndex(of: file) if let index { - tabgroup.files.insert(file, at: index) -// DispatchQueue.main.async { - tabgroup.files.remove(file) -// } - tabgroup.selected = file + tabgroup.openTab(item: newFile, at: index) } } Divider() diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 69eb30a017..5c917b70ab 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -35,7 +35,11 @@ final class TabGroupData: ObservableObject, Identifiable { weak var parent: WorkspaceSplitViewData? - init(files: OrderedSet = [], selected: WorkspaceClient.FileItem? = nil, parent: WorkspaceSplitViewData? = nil) { + init( + files: OrderedSet = [], + selected: WorkspaceClient.FileItem? = nil, + parent: WorkspaceSplitViewData? = nil + ) { self.files = files self.selected = selected ?? files.first self.parent = parent @@ -75,6 +79,40 @@ final class TabGroupData: ObservableObject, Identifiable { // } } + func openTab(item: WorkspaceClient.FileItem, at index: Int? = nil) { + Task { + await MainActor.run { + if let index { + files.insert(item, at: index) + } else { + files.append(item) + } + selected = item + do { + try openFile(item: item) + } catch { + Swift.print(error) + } + } + } + } + + private func openFile(item: WorkspaceClient.FileItem) throws { + guard item.fileDocument == nil else { + return + } + + let contentType = try item.url.resourceValues(forKeys: [.contentTypeKey]).contentType + let codeFile = try CodeFileDocument( + for: item.url, + withContentsOf: item.url, + ofType: contentType?.identifier ?? "" + ) + item.fileDocument = codeFile + CodeEditDocumentController.shared.addDocument(codeFile) + Swift.print("Opening file for item: ", item.url) + } + } extension TabGroupData: Equatable, Hashable { From 62fa64ac1e246e2e0ca5721f2a1873fbf486a479 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sat, 4 Mar 2023 03:40:03 +0100 Subject: [PATCH 30/82] small fixes Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/EditorSplitView.swift | 6 ++---- CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorSplitView.swift b/CodeEdit/Features/SplitView/EditorSplitView.swift index a7ac60c623..0074f4f1a3 100644 --- a/CodeEdit/Features/SplitView/EditorSplitView.swift +++ b/CodeEdit/Features/SplitView/EditorSplitView.swift @@ -78,12 +78,10 @@ struct EditorSplitView: NSViewControllerRepresentable { controller.splitViewItems = controller.items.map(\.item) if hasChanged && controller.splitViewItems.count > 1 { - print(controller.items.count, controller.splitView.frame.width) - print(controller.splitView.frame.width / CGFloat(controller.items.count)) - + let numerator = controller.splitView.isVertical ? controller.splitView.frame.width : controller.splitView.frame.height for idx in 0.. Date: Sat, 4 Mar 2023 03:42:25 +0100 Subject: [PATCH 31/82] renamed variables Signed-off-by: Wouter01 --- .../Controllers/CodeEditWindowController.swift | 2 +- CodeEdit/Features/Documents/WorkspaceDocument.swift | 4 ++-- .../InspectorSidebar/InspectorSidebarView.swift | 2 +- .../OutlineView/OutlineViewController.swift | 2 +- ...ntViewController+OutlineTableViewCellDelegate.swift | 2 +- .../ProjectNavigator/ProjectNavigatorView.swift | 2 +- CodeEdit/Features/SplitView/TabManager.swift | 10 +++++----- .../Features/SplitView/WorkspaceTabGroupView.swift | 4 ++-- CodeEdit/Features/Tabs/Views/TabBarItemView.swift | 2 +- CodeEdit/Features/Tabs/Views/TabBarView.swift | 6 +++--- CodeEdit/WorkspaceView.swift | 2 +- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index f80c59e036..a015b54d87 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -242,7 +242,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { } private func getSelectedCodeFile() -> CodeFileDocument? { - workspace?.tabManager.activeTab.selected?.fileDocument + workspace?.tabManager.activeTabGroup.selected?.fileDocument } @IBAction func saveDocument(_ sender: Any) { diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 30353e7028..203d50fff1 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -227,7 +227,7 @@ import CodeEditKit return } // Save unsaved changes before closing - let editedCodeFiles = tabManager.tabs.gatherOpenFiles().compactMap(\.fileDocument).filter(\.isDocumentEdited) + let editedCodeFiles = tabManager.tabGroups.gatherOpenFiles().compactMap(\.fileDocument).filter(\.isDocumentEdited) for editedCodeFile in editedCodeFiles { let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) shouldClose.initialize(to: true) @@ -252,7 +252,7 @@ import CodeEditKit implementation, to: (@convention(c)(Any, Selector, Any, Bool, UnsafeMutableRawPointer?) -> Void).self ) - let areAllOpenedCodeFilesClean = tabManager.tabs.gatherOpenFiles() + let areAllOpenedCodeFilesClean = tabManager.tabGroups.gatherOpenFiles() .compactMap(\.fileDocument) .allSatisfy { !$0.isDocumentEdited } function(object, shouldCloseSelector, self, areAllOpenedCodeFilesClean, contextInfo) diff --git a/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift b/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift index 612283ad97..226a4bef10 100644 --- a/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift +++ b/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift @@ -23,7 +23,7 @@ struct InspectorSidebarView: View { var body: some View { VStack { - if let path = tabManager.activeTab.selected?.fileDocument?.fileURL?.path(percentEncoded: false) { + if let path = tabManager.activeTabGroup.selected?.fileDocument?.fileURL?.path(percentEncoded: false) { switch selection { case 0: FileInspectorView( diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift index 3dcc06f010..7c96245b27 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift @@ -90,7 +90,7 @@ final class OutlineViewController: NSViewController { /// /// Most importantly when the `id` changes from an external view. func updateSelection() { - guard let itemID = workspace?.tabManager.activeTab.selected?.id else { + guard let itemID = workspace?.tabManager.activeTabGroup.selected?.id else { outlineView.deselectRow(outlineView.selectedRow) return } diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlintViewController+OutlineTableViewCellDelegate.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlintViewController+OutlineTableViewCellDelegate.swift index eebc6eb47c..9fc423f3f5 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlintViewController+OutlineTableViewCellDelegate.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlintViewController+OutlineTableViewCellDelegate.swift @@ -12,7 +12,7 @@ import Foundation extension OutlineViewController: OutlineTableViewCellDelegate { func moveFile(file: Item, to destination: URL) { if !file.isFolder { - workspace?.tabManager.tabs.closeAllTabs(of: file) + workspace?.tabManager.tabGroups.closeAllTabs(of: file) } file.move(to: destination) if !file.isFolder { diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift index bb2af391b5..755a02f3dd 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/ProjectNavigatorView.swift @@ -19,6 +19,6 @@ struct ProjectNavigatorView: View { @EnvironmentObject var tabManager: TabManager var body: some View { - OutlineView(selection: $tabManager.activeTab.selected) + OutlineView(selection: $tabManager.activeTabGroup.selected) } } diff --git a/CodeEdit/Features/SplitView/TabManager.swift b/CodeEdit/Features/SplitView/TabManager.swift index f94505ca91..db2c0c8f88 100644 --- a/CodeEdit/Features/SplitView/TabManager.swift +++ b/CodeEdit/Features/SplitView/TabManager.swift @@ -9,9 +9,9 @@ import Foundation import OrderedCollections class TabManager: ObservableObject { - @Published var tabs: TabGroup + @Published var tabGroups: TabGroup - @Published var activeTab: TabGroupData { + @Published var activeTabGroup: TabGroupData { didSet { activeTabHistory.updateOrInsert(oldValue, at: 0) } @@ -23,15 +23,15 @@ class TabManager: ObservableObject { init() { let tab = TabGroupData() - self.activeTab = tab + self.activeTabGroup = tab self.activeTabHistory.append(tab) - self.tabs = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) + self.tabGroups = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) } func openTab(item: WorkspaceClient.FileItem, in tabgroup: TabGroupData? = nil) { Task { await MainActor.run { - let tabgroup = tabgroup ?? activeTab + let tabgroup = tabgroup ?? activeTabGroup tabgroup.files.append(item) tabgroup.selected = item do { diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index 08b739c58a..5a24a53609 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -40,7 +40,7 @@ struct WorkspaceTabGroupView: View { TabBarView() .id("TabBarView" + tabgroup.id.uuidString) .environmentObject(tabgroup) - .environment(\.controlActiveState, tabgroup == tabManager.activeTab ? .key : .inactive) + .environment(\.controlActiveState, tabgroup == tabManager.activeTabGroup ? .key : .inactive) Divider() if let file = tabgroup.selected { @@ -60,7 +60,7 @@ struct WorkspaceTabGroupView: View { .focused($isFocused) .onChange(of: isFocused) { focused in if focused { - tabManager.activeTab = tabgroup + tabManager.activeTabGroup = tabgroup } } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index c52bb99b48..de7fe47412 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -97,7 +97,7 @@ struct TabBarItemView: View { /// Switch the active tab to current tab. private func switchAction() { // Only set the `selectedId` when they are not equal to avoid performance issue for now. - tabManager.activeTab = tabs + tabManager.activeTabGroup = tabs if tabs.selected != item { tabs.selected = item // if workspace.selectionState.selectedId != item.tabID { diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 694e1a65a0..231d185f23 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -427,13 +427,13 @@ struct TabBarView: View { private var leadingAccessories: some View { HStack(spacing: 2) { - if tabManager.tabs.findSomeTabGroup(except: tabs) != nil { + if tabManager.tabGroups.findSomeTabGroup(except: tabs) != nil { TabBarAccessoryIcon( icon: .init(systemName: "multiply"), action: { tabs.close() - if tabManager.activeTab == tabs { - tabManager.activeTab = tabManager.activeTabHistory.removeFirst() + if tabManager.activeTabGroup == tabs { + tabManager.activeTabGroup = tabManager.activeTabHistory.removeFirst() } } ) diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index d734e807c5..b32f82a6ae 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -39,7 +39,7 @@ struct WorkspaceView: View { SplitViewReader { proxy in SplitView(axis: .vertical) { - EditorView(tabgroup: tabManager.tabs) + EditorView(tabgroup: tabManager.tabGroups) .frame(maxWidth: .infinity, maxHeight: .infinity) .safeAreaInset(edge: .bottom, spacing: 0) { StatusBarView(proxy: proxy, collapsed: $terminalCollapsed) From 430c5a669e4979e40175a8cbfddb301cb9c22aad Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sat, 4 Mar 2023 14:59:19 +0100 Subject: [PATCH 32/82] Removed unneeded Task Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/TabManager.swift | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/CodeEdit/Features/SplitView/TabManager.swift b/CodeEdit/Features/SplitView/TabManager.swift index db2c0c8f88..eaff971eed 100644 --- a/CodeEdit/Features/SplitView/TabManager.swift +++ b/CodeEdit/Features/SplitView/TabManager.swift @@ -29,17 +29,13 @@ class TabManager: ObservableObject { } func openTab(item: WorkspaceClient.FileItem, in tabgroup: TabGroupData? = nil) { - Task { - await MainActor.run { - let tabgroup = tabgroup ?? activeTabGroup - tabgroup.files.append(item) - tabgroup.selected = item - do { - try openFile(item: item) - } catch { - Swift.print(error) - } - } + let tabgroup = tabgroup ?? activeTabGroup + tabgroup.files.append(item) + tabgroup.selected = item + do { + try openFile(item: item) + } catch { + print(error) } } From 7dc906e16762f8482544b57483034c66457cd94b Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sat, 4 Mar 2023 21:55:46 +0100 Subject: [PATCH 33/82] Fixed issue with NSSplitView Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/EditorSplitView.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorSplitView.swift b/CodeEdit/Features/SplitView/EditorSplitView.swift index 0074f4f1a3..39dc3af71a 100644 --- a/CodeEdit/Features/SplitView/EditorSplitView.swift +++ b/CodeEdit/Features/SplitView/EditorSplitView.swift @@ -52,7 +52,7 @@ struct EditorSplitView: NSViewControllerRepresentable { var viewController: SplitViewController func makeNSViewController(context: Context) -> SplitViewController { - viewController.items = children.map { SplitViewItem(child: $0) } +// viewController.items = children.map { SplitViewItem(child: $0) } return viewController } @@ -76,10 +76,15 @@ struct EditorSplitView: NSViewControllerRepresentable { } controller.splitViewItems = controller.items.map(\.item) - if hasChanged && controller.splitViewItems.count > 1 { let numerator = controller.splitView.isVertical ? controller.splitView.frame.width : controller.splitView.frame.height - for idx in 0.. Date: Sat, 4 Mar 2023 23:48:34 +0100 Subject: [PATCH 34/82] Added functionality to split button Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 4 + .../CodeEditWindowController.swift | 8 +- .../Keybindings/ModifierKeysObserver.swift | 132 ++++++++++++++++++ CodeEdit/Features/Tabs/Views/TabBarView.swift | 46 +++++- 4 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 CodeEdit/Features/Keybindings/ModifierKeysObserver.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 17a044b263..d42987702f 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -325,6 +325,7 @@ 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; }; 6C7256D729A3D7D000C2D3E0 /* EditorSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */; }; 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */; }; + 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */; }; 6C91D57229B176FF0059A90D /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C91D57129B176FF0059A90D /* TabManager.swift */; }; 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */; }; 6C97EBCF297876E500302F95 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCE297876E500302F95 /* AboutWindowController.swift */; }; @@ -718,6 +719,7 @@ 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ContentInsets.swift"; sourceTree = ""; }; 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorSplitView.swift; sourceTree = ""; }; + 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifierKeysObserver.swift; sourceTree = ""; }; 6C91D57129B176FF0059A90D /* TabManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManager.swift; sourceTree = ""; }; 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; 6C97EBCE297876E500302F95 /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; @@ -1823,6 +1825,7 @@ isa = PBXGroup; children = ( 58A5DF9F29339F6400D1BD5D /* CommandManager.swift */, + 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */, 58A5DFA129339F6400D1BD5D /* default_keybindings.json */, 58A5DF9E29339F6400D1BD5D /* KeybindingManager.swift */, ); @@ -2875,6 +2878,7 @@ 58F2EAF0292FB2B0004A9BDE /* PreviewThemeView.swift in Sources */, 58F2EAFF292FB2B0004A9BDE /* KeybindingsPreferencesView.swift in Sources */, 6CDA84AD284C1BA000C1CC3A /* TabBarContextMenu.swift in Sources */, + 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */, 0463E51127FCC1DF00806D5C /* CodeEditAPI.swift in Sources */, 587B9E8129301D8F00AC7927 /* PublicKey.swift in Sources */, 58F2EB0B292FB2B0004A9BDE /* AccountsPreferences.swift in Sources */, diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index a015b54d87..54da9f72e5 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -83,9 +83,11 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { splitVC.addSplitViewItem(navigator) let workspaceView = WindowObserver(window: window!) { - WorkspaceView() - .environmentObject(workspace) - .environmentObject(workspace.tabManager) + EventModifierObserver { + WorkspaceView() + .environmentObject(workspace) + .environmentObject(workspace.tabManager) + } } let mainContent = NSSplitViewItem( diff --git a/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift b/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift new file mode 100644 index 0000000000..9d336e24c8 --- /dev/null +++ b/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift @@ -0,0 +1,132 @@ +// +// ModifierKeysObserver.swift +// CodeEdit +// +// Created by Wouter Hennen on 04/03/2023. +// + +import SwiftUI +import Combine + +struct EventModifierObserver: View { + @ViewBuilder var content: Content + + @State var modifierFlags: NSEvent.ModifierFlags = [] + + var body: some View { + content + .environment(\.modifierKeys, modifierFlags.intersection(.deviceIndependentFlagsMask)) + .onReceive(NSEvent.publisher(scope: .local, matching: .flagsChanged)) { output in + modifierFlags = output.modifierFlags + } + } +} + +struct EventModifierEnvironmentKey: EnvironmentKey { + static var defaultValue: NSEvent.ModifierFlags = [] +} + +extension EnvironmentValues { + var modifierKeys: EventModifierEnvironmentKey.Value { + get { self[EventModifierEnvironmentKey.self] } + set { self[EventModifierEnvironmentKey.self] = newValue } + } +} + +extension NSEvent { + static func publisher(scope: Publisher.Scope, matching: EventTypeMask) -> Publisher { + return Publisher(scope: scope, matching: matching) + } + + public struct Publisher: Combine.Publisher { + public enum Scope { + case local, global + } + + public typealias Output = NSEvent + + public typealias Failure = Never + + let scope: Scope + let matching: EventTypeMask + + init(scope: Scope, matching: EventTypeMask) { + self.scope = scope + self.matching = matching + } + + public func receive(subscriber: S) where S: Subscriber, Self.Failure == S.Failure, Self.Output == S.Input { + let subscription = Subscription(scope: scope, matching: matching, subscriber: subscriber) + subscriber.receive(subscription: subscription) + } + } +} + +private extension NSEvent.Publisher { + final class Subscription where S.Input == NSEvent, S.Failure == Never { + fileprivate let lock = NSLock() + fileprivate var demand = Subscribers.Demand.none + private var monitor: Any? + + fileprivate let subscriberLock = NSRecursiveLock() + + init(scope: Scope, matching: NSEvent.EventTypeMask, subscriber: S) { + switch scope { + case .local: + self.monitor = NSEvent.addLocalMonitorForEvents(matching: matching) { [weak self] (event) -> NSEvent? in + self?.didReceive(event: event, subscriber: subscriber) + return event + } + + case .global: + self.monitor = NSEvent.addGlobalMonitorForEvents(matching: matching) { [weak self] in + self?.didReceive(event: $0, subscriber: subscriber) + } + } + + } + + deinit { + if let monitor = monitor { + NSEvent.removeMonitor(monitor) + } + } + + func didReceive(event: NSEvent, subscriber: S) { + let val = { () -> Subscribers.Demand in + lock.lock() + defer { lock.unlock() } + let before = demand + if demand > 0 { + demand -= 1 + } + return before + }() + + guard val > 0 else { return } + + let newDemand = subscriber.receive(event) + + lock.lock() + demand += newDemand + lock.unlock() + } + } +} + +extension NSEvent.Publisher.Subscription: Combine.Subscription { + func request(_ demand: Subscribers.Demand) { + lock.lock() + defer { lock.unlock() } + self.demand += demand + } + + func cancel() { + lock.lock() + defer { lock.unlock() } + guard let monitor = monitor else { return } + + self.monitor = nil + NSEvent.removeMonitor(monitor) + } +} diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 231d185f23..ac4adff006 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -14,6 +14,8 @@ import SwiftUI // - TODO: TabBarItemView drop-outside event handler. struct TabBarView: View { + @Environment(\.modifierKeys) var modifierKeys + typealias TabID = WorkspaceClient.FileItem.ID /// The height of tab bar. @@ -35,6 +37,8 @@ struct TabBarView: View { @EnvironmentObject private var tabs: TabGroupData + @Environment(\.splitEditor) var splitEditor + /// The app preference. @StateObject private var prefs: AppPreferencesModel = .shared @@ -471,6 +475,12 @@ struct TabBarView: View { } } +// var splitViewAxisButton: Axis { +// switch (tabs.parent?.axis, modifierKeys) { +// case (.horizontal, .option) +// } +// } + private var trailingAccessories: some View { HStack(spacing: 2) { TabBarAccessoryIcon( @@ -487,13 +497,7 @@ struct TabBarView: View { .foregroundColor(.secondary) .buttonStyle(.plain) .help("Enable Code Review") - TabBarAccessoryIcon( - icon: .init(systemName: "square.split.2x1"), - action: {} // TODO: Implement - ) - .foregroundColor(.secondary) - .buttonStyle(.plain) - .help("Split View") + splitviewButton } .padding(.horizontal, 7) .opacity(activeState != .inactive ? 1.0 : 0.5) @@ -505,6 +509,34 @@ struct TabBarView: View { } } + var splitviewButton: some View { + Group { + switch (tabs.parent!.axis, modifierKeys.contains(.option)) { + case (.horizontal, true), (.vertical, false): + TabBarAccessoryIcon(icon: Image(systemName: "square.split.1x2")) { + split(edge: .bottom) + } + .help("Split Vertically") + + case (.vertical, true), (.horizontal, false): + TabBarAccessoryIcon(icon: Image(systemName: "square.split.2x1")) { + split(edge: .trailing) + } + .help("Split Horizontally") + } + } + .foregroundColor(.secondary) + .buttonStyle(.plain) + } + + func split(edge: Edge) { + if let tab = tabs.selected { + splitEditor(edge, .init(files: [tab])) + } else { + splitEditor(edge, .init()) + } + } + private struct TabBarItemOnDropDelegate: DropDelegate { private let currentTabId: TabID @Binding From f0c55a8df41d885498b05d8b0fb70c924a2beff3 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 00:35:47 +0100 Subject: [PATCH 35/82] Fixed issue with breadcrumbsview Signed-off-by: Wouter01 --- .../Views/BreadcrumbsComponent.swift | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift index 34c2b54968..dc2c322c22 100644 --- a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift +++ b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift @@ -6,27 +6,28 @@ // import SwiftUI +import Combine struct BreadcrumbsComponent: View { - + private let fileItem: WorkspaceClient.FileItem private let tappedOpenFile: (WorkspaceClient.FileItem) -> Void - + @Environment(\.colorScheme) var colorScheme - + @Environment(\.controlActiveState) private var activeState - + @StateObject private var prefs: AppPreferencesModel = .shared - + @State var position: NSPoint? - + @State var selection: WorkspaceClient.FileItem - + init( fileItem: WorkspaceClient.FileItem, tappedOpenFile: @escaping (WorkspaceClient.FileItem) -> Void @@ -35,7 +36,7 @@ struct BreadcrumbsComponent: View { self._selection = .init(wrappedValue: fileItem) self.tappedOpenFile = tappedOpenFile } - + var siblings: [WorkspaceClient.FileItem] { if let siblings = fileItem.parent?.children?.sortItems(foldersOnTop: true), !siblings.isEmpty { return siblings @@ -43,7 +44,7 @@ struct BreadcrumbsComponent: View { return [fileItem] } } - + var body: some View { NSPopUpButtonView(selection: $selection) { let button = NSPopUpButton() @@ -55,23 +56,26 @@ struct BreadcrumbsComponent: View { } .padding(.leading, -5) } - + struct NSPopUpButtonView: NSViewRepresentable where ItemType: Equatable { @Binding var selection: ItemType var popupCreator: () -> NSPopUpButton - + typealias NSViewType = NSPopUpButton - + func makeNSView(context: NSViewRepresentableContext) -> NSPopUpButton { let newPopupButton = popupCreator() setPopUpFromSelection(newPopupButton, selection: selection) + if let menu = newPopupButton.menu { + context.coordinator.registerForChanges(in: menu) + } return newPopupButton } - + func updateNSView(_ nsView: NSPopUpButton, context: NSViewRepresentableContext) { setPopUpFromSelection(nsView, selection: selection) } - + func setPopUpFromSelection(_ button: NSPopUpButton, selection: ItemType) { let itemsList = button.itemArray let matchedMenuItem = itemsList.filter { @@ -81,30 +85,30 @@ struct BreadcrumbsComponent: View { button.select(matchedMenuItem) } } - + func makeCoordinator() -> Coordinator { return Coordinator(self) } - + class Coordinator: NSObject { - var parent: NSPopUpButtonView! - + var parent: NSPopUpButtonView + + var cancellable: AnyCancellable? + init(_ parent: NSPopUpButtonView) { - super.init() self.parent = parent - NotificationCenter.default.addObserver( - self, - selector: #selector(dropdownItemSelected), - name: NSMenu.didSendActionNotification, - object: nil - ) + super.init() } - - @objc func dropdownItemSelected(_ notification: NSNotification) { - let menuItem = (notification.userInfo?["MenuItem"])! as? NSMenuItem - if let selection = menuItem?.representedObject as? ItemType { - parent.selection = selection - } + + func registerForChanges(in menu: NSMenu) { + cancellable = NotificationCenter.default + .publisher(for: NSMenu.didSendActionNotification, object: menu) + .sink { notification in + if let menuItem = notification.userInfo?["MenuItem"] as? NSMenuItem, + let selection = menuItem as? ItemType { + self.parent.selection = selection + } + } } } } From d568f290851a737b4ace60fba4193af222b57a9b Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 01:02:36 +0100 Subject: [PATCH 36/82] breadcrumbs are grayed out when out of focus Signed-off-by: Wouter01 --- .../Views/BreadcrumbsComponent.swift | 38 +++++++++---------- .../Breadcrumbs/Views/BreadcrumbsView.swift | 2 + .../SplitView/WorkspaceTabGroupView.swift | 2 +- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift index dc2c322c22..951fa69521 100644 --- a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift +++ b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift @@ -9,25 +9,25 @@ import SwiftUI import Combine struct BreadcrumbsComponent: View { - + private let fileItem: WorkspaceClient.FileItem private let tappedOpenFile: (WorkspaceClient.FileItem) -> Void - + @Environment(\.colorScheme) var colorScheme - + @Environment(\.controlActiveState) private var activeState - + @StateObject private var prefs: AppPreferencesModel = .shared - + @State var position: NSPoint? - + @State var selection: WorkspaceClient.FileItem - + init( fileItem: WorkspaceClient.FileItem, tappedOpenFile: @escaping (WorkspaceClient.FileItem) -> Void @@ -36,7 +36,7 @@ struct BreadcrumbsComponent: View { self._selection = .init(wrappedValue: fileItem) self.tappedOpenFile = tappedOpenFile } - + var siblings: [WorkspaceClient.FileItem] { if let siblings = fileItem.parent?.children?.sortItems(foldersOnTop: true), !siblings.isEmpty { return siblings @@ -44,7 +44,7 @@ struct BreadcrumbsComponent: View { return [fileItem] } } - + var body: some View { NSPopUpButtonView(selection: $selection) { let button = NSPopUpButton() @@ -56,13 +56,13 @@ struct BreadcrumbsComponent: View { } .padding(.leading, -5) } - + struct NSPopUpButtonView: NSViewRepresentable where ItemType: Equatable { @Binding var selection: ItemType var popupCreator: () -> NSPopUpButton - + typealias NSViewType = NSPopUpButton - + func makeNSView(context: NSViewRepresentableContext) -> NSPopUpButton { let newPopupButton = popupCreator() setPopUpFromSelection(newPopupButton, selection: selection) @@ -71,11 +71,11 @@ struct BreadcrumbsComponent: View { } return newPopupButton } - + func updateNSView(_ nsView: NSPopUpButton, context: NSViewRepresentableContext) { setPopUpFromSelection(nsView, selection: selection) } - + func setPopUpFromSelection(_ button: NSPopUpButton, selection: ItemType) { let itemsList = button.itemArray let matchedMenuItem = itemsList.filter { @@ -85,21 +85,21 @@ struct BreadcrumbsComponent: View { button.select(matchedMenuItem) } } - + func makeCoordinator() -> Coordinator { return Coordinator(self) } - + class Coordinator: NSObject { var parent: NSPopUpButtonView - + var cancellable: AnyCancellable? - + init(_ parent: NSPopUpButtonView) { self.parent = parent super.init() } - + func registerForChanges(in menu: NSMenu) { cancellable = NotificationCenter.default .publisher(for: NSMenu.didSendActionNotification, object: menu) diff --git a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift index d8018334c5..e2d2a45aba 100644 --- a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift +++ b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift @@ -54,6 +54,8 @@ struct BreadcrumbsView: View { .padding(.horizontal, 10) } .frame(height: Self.height, alignment: .center) + .saturation(activeState == .inactive ? 0.0 : 1.0) + .brightness(activeState == .inactive ? -0.1 : 0.0) .background(EffectView(.headerView).frame(height: Self.height)) } diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index 5a24a53609..e388bf9ca1 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -40,7 +40,6 @@ struct WorkspaceTabGroupView: View { TabBarView() .id("TabBarView" + tabgroup.id.uuidString) .environmentObject(tabgroup) - .environment(\.controlActiveState, tabgroup == tabManager.activeTabGroup ? .key : .inactive) Divider() if let file = tabgroup.selected { @@ -55,6 +54,7 @@ struct WorkspaceTabGroupView: View { Divider() } } + .environment(\.controlActiveState, tabgroup == tabManager.activeTabGroup ? .key : .inactive) .background(EffectView(.titlebar, blendingMode: .withinWindow, emphasized: false)) } .focused($isFocused) From b456694c76c286c8e9f62dac4b05296f3b78a06d Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 01:56:49 +0100 Subject: [PATCH 37/82] Added forward/backward functionality Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 8 ++++++ .../Features/Tabs/TabGroup/TabGroupData.swift | 26 ++++++++++++++----- .../Features/Tabs/Views/TabBarItemView.swift | 4 +++ CodeEdit/Features/Tabs/Views/TabBarView.swift | 12 ++++++--- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index d42987702f..ea00a335c9 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -326,6 +326,7 @@ 6C7256D729A3D7D000C2D3E0 /* EditorSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */; }; 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */; }; 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */; }; + 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 6C81916A29B41DD300B75C92 /* DequeModule */; }; 6C91D57229B176FF0059A90D /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C91D57129B176FF0059A90D /* TabManager.swift */; }; 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */; }; 6C97EBCF297876E500302F95 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCE297876E500302F95 /* AboutWindowController.swift */; }; @@ -776,6 +777,7 @@ 5879828A292ED15F0085B254 /* SwiftTerm in Frameworks */, 2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */, 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */, + 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2378,6 +2380,7 @@ 58F2EB1D292FB954004A9BDE /* Sparkle */, 6C147C4429A329350089B630 /* OrderedCollections */, 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */, + 6C81916A29B41DD300B75C92 /* DequeModule */, ); productName = CodeEdit; productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */; @@ -3922,6 +3925,11 @@ package = 6C7256D829A3D98C00C2D3E0 /* XCRemoteSwiftPackageReference "SequenceBuilder" */; productName = SequenceBuilder; }; + 6C81916A29B41DD300B75C92 /* DequeModule */ = { + isa = XCSwiftPackageProductDependency; + package = 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */; + productName = DequeModule; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B658FB2427DA9E0F00EA4DBD /* Project object */; diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 5c917b70ab..8a75cf7e69 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -7,15 +7,20 @@ import Foundation import OrderedCollections +import DequeModule final class TabGroupData: ObservableObject, Identifiable { - @Published var files: OrderedSet = [] { + typealias Tab = WorkspaceClient.FileItem + + @Published var files: OrderedSet = [] { didSet { let change = files.symmetricDifference(oldValue) if files.count > oldValue.count { // Amount of tabs grew, so set the first new as selected. selected = change.first + history.prepend(contentsOf: change) + historyOffset = 0 } else { // Selected file was removed if let selected, change.contains(selected) { @@ -29,15 +34,22 @@ final class TabGroupData: ObservableObject, Identifiable { } } - @Published var selected: WorkspaceClient.FileItem? + @Published var history: Deque = [] + @Published var historyOffset: Int = 0 { + didSet { + selected = history[historyOffset] + } + } + + @Published var selected: Tab? let id = UUID() weak var parent: WorkspaceSplitViewData? init( - files: OrderedSet = [], - selected: WorkspaceClient.FileItem? = nil, + files: OrderedSet = [], + selected: Tab? = nil, parent: WorkspaceSplitViewData? = nil ) { self.files = files @@ -49,7 +61,7 @@ final class TabGroupData: ObservableObject, Identifiable { parent?.closeTabGroup(with: id) } - func closeTab(item: WorkspaceClient.FileItem) { + func closeTab(item: Tab) { guard let file = item.fileDocument else { return } if file.isDocumentEdited { @@ -79,7 +91,7 @@ final class TabGroupData: ObservableObject, Identifiable { // } } - func openTab(item: WorkspaceClient.FileItem, at index: Int? = nil) { + func openTab(item: Tab, at index: Int? = nil) { Task { await MainActor.run { if let index { @@ -97,7 +109,7 @@ final class TabGroupData: ObservableObject, Identifiable { } } - private func openFile(item: WorkspaceClient.FileItem) throws { + private func openFile(item: Tab) throws { guard item.fileDocument == nil else { return } diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index de7fe47412..e60e245717 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -100,6 +100,10 @@ struct TabBarItemView: View { tabManager.activeTabGroup = tabs if tabs.selected != item { tabs.selected = item + tabs.history.removeFirst(tabs.historyOffset) + tabs.history.prepend(item) + tabs.historyOffset = 0 + // if workspace.selectionState.selectedId != item.tabID { // workspace.switchedTab(item: item) // TODO: Fix save state diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index ac4adff006..b77a59b52c 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -452,21 +452,27 @@ struct TabBarView: View { TabBarAccessoryIcon( icon: .init(systemName: "chevron.left"), - action: {} // TODO: Implement + action: { + tabs.historyOffset += 1 + } // TODO: Implement ) + .disabled(tabs.historyOffset == tabs.history.count-1) .foregroundColor(.secondary) .buttonStyle(.plain) .help("Navigate back") TabBarAccessoryIcon( icon: .init(systemName: "chevron.right"), - action: {} // TODO: Implement + action: { + tabs.historyOffset -= 1 + } // TODO: Implement ) + .disabled(tabs.historyOffset == 0) .foregroundColor(.secondary) .buttonStyle(.plain) .help("Navigate forward") } .padding(.horizontal, 7) - .opacity(activeState != .inactive ? 1.0 : 0.5) + .brightness(activeState == .inactive ? -0.3 : 0) .frame(maxHeight: .infinity) // Fill out vertical spaces. .background { if prefs.preferences.general.tabBarStyle == .native { From 6d17cbc0a8ac69104a837e568190810fa9eec385 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 02:00:26 +0100 Subject: [PATCH 38/82] small cleanup Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarView.swift | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index b77a59b52c..51f95cfcae 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -441,8 +441,6 @@ struct TabBarView: View { } } ) - .foregroundColor(.secondary) - .buttonStyle(.plain) .help("Close Tab Group") Divider() @@ -450,27 +448,20 @@ struct TabBarView: View { .padding(.horizontal, 4) } - TabBarAccessoryIcon( - icon: .init(systemName: "chevron.left"), - action: { - tabs.historyOffset += 1 - } // TODO: Implement - ) + TabBarAccessoryIcon(icon: .init(systemName: "chevron.left")) { + tabs.historyOffset += 1 + } .disabled(tabs.historyOffset == tabs.history.count-1) - .foregroundColor(.secondary) - .buttonStyle(.plain) .help("Navigate back") - TabBarAccessoryIcon( - icon: .init(systemName: "chevron.right"), - action: { - tabs.historyOffset -= 1 - } // TODO: Implement - ) + + TabBarAccessoryIcon(icon: .init(systemName: "chevron.right")) { + tabs.historyOffset -= 1 + } .disabled(tabs.historyOffset == 0) - .foregroundColor(.secondary) - .buttonStyle(.plain) .help("Navigate forward") } + .foregroundColor(.secondary) + .buttonStyle(.plain) .padding(.horizontal, 7) .brightness(activeState == .inactive ? -0.3 : 0) .frame(maxHeight: .infinity) // Fill out vertical spaces. From 520d9676fa275dd77535351b4c23ef9bc828a856 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 02:27:04 +0100 Subject: [PATCH 39/82] Added custom move buttonstyle Signed-off-by: Wouter01 --- .../Features/Tabs/Views/TabBarAccessory.swift | 34 +++++++++++++++++++ CodeEdit/Features/Tabs/Views/TabBarView.swift | 4 ++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift b/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift index 74f2c17eb0..a5dee6057f 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift @@ -34,6 +34,40 @@ struct TabBarAccessoryIcon: View { } } +extension ButtonStyle where Self == MoveButtonStyle { + static func move(_ edge: Edge, offset: CGFloat = 2.0) -> MoveButtonStyle { + MoveButtonStyle(edge: edge, value: offset) + } +} + +struct MoveButtonStyle: ButtonStyle { + + @Environment(\.isEnabled) var isEnabled + + let edge: Edge + let value: CGFloat + + var offset: CGSize { + switch edge { + case .top: + return .init(width: 0, height: -value) + case .leading: + return .init(width: -value, height: 0) + case .bottom: + return .init(width: 0, height: value) + case .trailing: + return .init(width: value, height: 0) + } + } + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .offset(configuration.isPressed ? offset : .zero) + .animation(.interactiveSpring(), value: configuration.isPressed) + .brightness(isEnabled ? 0.0 : -0.3) + } +} + /// Tab bar accessory area background for native tab bar style. struct TabBarAccessoryNativeBackground: View { enum DividerPosition { diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 51f95cfcae..190783467a 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -451,12 +451,14 @@ struct TabBarView: View { TabBarAccessoryIcon(icon: .init(systemName: "chevron.left")) { tabs.historyOffset += 1 } - .disabled(tabs.historyOffset == tabs.history.count-1) + .buttonStyle(.move(.leading)) + .disabled(tabs.historyOffset == tabs.history.count-1 || tabs.history.isEmpty) .help("Navigate back") TabBarAccessoryIcon(icon: .init(systemName: "chevron.right")) { tabs.historyOffset -= 1 } + .buttonStyle(.move(.trailing)) .disabled(tabs.historyOffset == 0) .help("Navigate forward") } From aefd004c0e1ff9804383626f4b528f2274b11a9e Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 04:16:25 +0100 Subject: [PATCH 40/82] Fixed some logic, removed animations Signed-off-by: Wouter01 --- .../OutlineView/OutlineViewController.swift | 2 +- .../SplitView/WorkspaceTabGroupView.swift | 2 - .../Features/Tabs/TabGroup/TabGroupData.swift | 81 ++++++++++++++----- .../Features/Tabs/Views/TabBarAccessory.swift | 34 -------- .../Tabs/Views/TabBarContextMenu.swift | 2 +- .../Features/Tabs/Views/TabBarItemView.swift | 2 +- CodeEdit/Features/Tabs/Views/TabBarView.swift | 31 ++++--- CodeEdit/WorkspaceView.swift | 1 - 8 files changed, 79 insertions(+), 76 deletions(-) diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift index 7c96245b27..6a13f4dfd7 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift @@ -276,7 +276,7 @@ extension OutlineViewController: NSOutlineViewDelegate { guard let item = outlineView.item(atRow: selectedIndex) as? Item else { return } if item.children == nil && shouldSendSelectionUpdate { - workspace?.tabManager.openTab(item: item) + workspace?.tabManager.activeTabGroup.openTab(item: item) } } diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index e388bf9ca1..c27f61484b 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -44,8 +44,6 @@ struct WorkspaceTabGroupView: View { Divider() if let file = tabgroup.selected { BreadcrumbsView(file: file) { newFile in - print("Opening \(newFile.fileName)") - let index = tabgroup.files.firstIndex(of: file) if let index { tabgroup.openTab(item: newFile, at: index) diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 8a75cf7e69..7d0983a25c 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -19,25 +19,47 @@ final class TabGroupData: ObservableObject, Identifiable { if files.count > oldValue.count { // Amount of tabs grew, so set the first new as selected. selected = change.first - history.prepend(contentsOf: change) - historyOffset = 0 } else { // Selected file was removed if let selected, change.contains(selected) { if let oldIndex = oldValue.firstIndex(of: selected), oldIndex - 1 < files.count, !files.isEmpty { self.selected = files[max(0, oldIndex-1)] + print("Selection becomes \(self.selected?.fileName)") } else { self.selected = nil + print("Selection becomes nil") } } } } } - @Published var history: Deque = [] + @Published var history: Deque = [] { + didSet { + print(history.map(\.fileName)) + } + } @Published var historyOffset: Int = 0 { didSet { - selected = history[historyOffset] + print("offset going to", historyOffset) + let tab = history[historyOffset] + +// guard historyOffset != 0 else { +// selected = tab +// return +// } + + if !files.contains(tab) { +// + if let selected { + openTab(item: tab, at: files.firstIndex(of: selected), fromHistory: true) + } else { + openTab(item: tab, fromHistory: true) + } +// history.removeFirst() + + } + selected = tab } } @@ -62,6 +84,19 @@ final class TabGroupData: ObservableObject, Identifiable { } func closeTab(item: Tab) { + print("Closing tab...") + if item != selected { + history.prepend(item) + } + files.remove(item) + history.prepend(selected!) + historyOffset = 0 + print("Closed tab.") +// history.removeFirst() + // if let selected { +// history.prepend(item) + // } + guard let file = item.fileDocument else { return } if file.isDocumentEdited { @@ -80,7 +115,8 @@ final class TabGroupData: ObservableObject, Identifiable { return } } - files.remove(item) + + // if openedTabsFromState { // var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] @@ -91,21 +127,26 @@ final class TabGroupData: ObservableObject, Identifiable { // } } - func openTab(item: Tab, at index: Int? = nil) { - Task { - await MainActor.run { - if let index { - files.insert(item, at: index) - } else { - files.append(item) - } - selected = item - do { - try openFile(item: item) - } catch { - Swift.print(error) - } - } + func openTab(item: Tab, at index: Int? = nil, fromHistory: Bool = false) { + if let index { + files.insert(item, at: index) + } else { + files.append(item) + } + selected = item + if fromHistory { + print("Opening from history") + print(history.map(\.fileName)) +// historyOffset += 1 + } else { + history.removeFirst(historyOffset) + history.prepend(item) + historyOffset = 0 + } + do { + try openFile(item: item) + } catch { + Swift.print(error) } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift b/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift index a5dee6057f..74f2c17eb0 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift @@ -34,40 +34,6 @@ struct TabBarAccessoryIcon: View { } } -extension ButtonStyle where Self == MoveButtonStyle { - static func move(_ edge: Edge, offset: CGFloat = 2.0) -> MoveButtonStyle { - MoveButtonStyle(edge: edge, value: offset) - } -} - -struct MoveButtonStyle: ButtonStyle { - - @Environment(\.isEnabled) var isEnabled - - let edge: Edge - let value: CGFloat - - var offset: CGSize { - switch edge { - case .top: - return .init(width: 0, height: -value) - case .leading: - return .init(width: -value, height: 0) - case .bottom: - return .init(width: 0, height: value) - case .trailing: - return .init(width: value, height: 0) - } - } - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .offset(configuration.isPressed ? offset : .zero) - .animation(.interactiveSpring(), value: configuration.isPressed) - .brightness(isEnabled ? 0.0 : -0.3) - } -} - /// Tab bar accessory area background for native tab bar style. struct TabBarAccessoryNativeBackground: View { enum DividerPosition { diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index 3f91b66486..90ef574240 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -138,7 +138,7 @@ struct TabBarContextMenu: ViewModifier { func moveToNewSplit(_ edge: Edge) { let newTabGroup = TabGroupData(files: [item]) splitEditor(edge, newTabGroup) - tabs.files.remove(item) + tabs.closeTab(item: item) } /// Copies the relative path from the workspace folder to the given file item to the pasteboard. diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index e60e245717..3e46159073 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -116,7 +116,7 @@ struct TabBarItemView: View { withAnimation( .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) ) { - tabs.files.remove(item) + tabs.closeTab(item: item) } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 190783467a..fd694c5a65 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -451,14 +451,12 @@ struct TabBarView: View { TabBarAccessoryIcon(icon: .init(systemName: "chevron.left")) { tabs.historyOffset += 1 } - .buttonStyle(.move(.leading)) .disabled(tabs.historyOffset == tabs.history.count-1 || tabs.history.isEmpty) .help("Navigate back") TabBarAccessoryIcon(icon: .init(systemName: "chevron.right")) { tabs.historyOffset -= 1 } - .buttonStyle(.move(.trailing)) .disabled(tabs.historyOffset == 0) .help("Navigate forward") } @@ -508,24 +506,25 @@ struct TabBarView: View { } } - var splitviewButton: some View { - Group { - switch (tabs.parent!.axis, modifierKeys.contains(.option)) { - case (.horizontal, true), (.vertical, false): - TabBarAccessoryIcon(icon: Image(systemName: "square.split.1x2")) { - split(edge: .bottom) - } - .help("Split Vertically") + var splitViewButtonAxis: Axis { + switch (tabs.parent!.axis, modifierKeys.contains(.option)) { + case (.horizontal, true), (.vertical, false): + return .vertical - case (.vertical, true), (.horizontal, false): - TabBarAccessoryIcon(icon: Image(systemName: "square.split.2x1")) { - split(edge: .trailing) - } - .help("Split Horizontally") - } + case (.vertical, true), (.horizontal, false): + return .horizontal + } + } + + var splitviewButton: some View { + TabBarAccessoryIcon(icon: Image(systemName: "square.split.2x1")) { + split(edge: splitViewButtonAxis == .vertical ? .bottom : .trailing) } + .rotationEffect(splitViewButtonAxis == .vertical ? .degrees(90) : .zero) + .animation(.interactiveSpring(), value: splitViewButtonAxis) .foregroundColor(.secondary) .buttonStyle(.plain) + .help("Split \(splitViewButtonAxis == .vertical ? "Vertically" : "Horizontally")") } func split(edge: Edge) { diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index b32f82a6ae..1050fe09da 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -44,7 +44,6 @@ struct WorkspaceView: View { .safeAreaInset(edge: .bottom, spacing: 0) { StatusBarView(proxy: proxy, collapsed: $terminalCollapsed) } - .layoutPriority(2) StatusBarDrawer() .collapsable() From 0cbd085db55e06f03ec7ebad6c619e7bc6857481 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 04:43:10 +0100 Subject: [PATCH 41/82] renamed split buttons Signed-off-by: Wouter01 --- .../Features/Tabs/Views/TabBarContextMenu.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index 90ef574240..d9877289ca 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -38,18 +38,18 @@ struct TabBarContextMenu: ViewModifier { func body(content: Content) -> some View { content.contextMenu(menuItems: { Group { - Button("Split and open on the right") { - moveToNewSplit(.trailing) + Button("Split Up") { + moveToNewSplit(.top) } - Button("Split and open on the bottom") { + Button("Split Down") { moveToNewSplit(.bottom) } - Button("Split and open on the top") { - moveToNewSplit(.top) - } - Button("Split and open on the left") { + Button("Split Left") { moveToNewSplit(.leading) } + Button("Split Right") { + moveToNewSplit(.trailing) + } Button("Close Tab") { withAnimation { tabs.closeTab(item: item) From 1352f19486c8bcfc08fdec05f0bc190b2b15f390 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 12:58:31 +0100 Subject: [PATCH 42/82] Fixed abug where the app would freeze if file contents are empty Signed-off-by: Wouter01 --- CodeEdit/Features/CodeFile/CodeFileView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Features/CodeFile/CodeFileView.swift b/CodeEdit/Features/CodeFile/CodeFileView.swift index 909fe99e20..24643d873a 100644 --- a/CodeEdit/Features/CodeFile/CodeFileView.swift +++ b/CodeEdit/Features/CodeFile/CodeFileView.swift @@ -93,7 +93,8 @@ struct CodeFileView: View { } } .disabled(!editable) - .frame(maxHeight: .infinity) + // minHeight zero fixes a bug where the app would freeze if the contents of the file are empty. + .frame(minHeight: .zero, maxHeight: .infinity) .onChange(of: ThemeModel.shared.selectedTheme) { newValue in guard let theme = newValue else { return } self.selectedTheme = theme From 7d46414c1da083919ecd3c6c536bb77fb53acd5e Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 14:52:30 +0100 Subject: [PATCH 43/82] Fixed temporary tabs Signed-off-by: Wouter01 --- .../OutlineView/OutlineViewController.swift | 3 +- .../Features/Tabs/TabGroup/TabGroupData.swift | 47 ++++++++++++++++--- .../Features/Tabs/Views/TabBarItemView.swift | 15 +++--- CodeEdit/Features/Tabs/Views/TabBarView.swift | 12 +++-- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift index 6a13f4dfd7..d5a104ad5b 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift @@ -111,6 +111,7 @@ final class OutlineViewController: NSViewController { } } else { // TODO: Fix temporary tab + workspace?.tabManager.activeTabGroup.openTab(item: item, asTemporary: false) // if workspace?.selectionState.temporaryTab == item.tabID { // workspace?.convertTemporaryTab() // } @@ -276,7 +277,7 @@ extension OutlineViewController: NSOutlineViewDelegate { guard let item = outlineView.item(atRow: selectedIndex) as? Item else { return } if item.children == nil && shouldSendSelectionUpdate { - workspace?.tabManager.activeTabGroup.openTab(item: item) + workspace?.tabManager.activeTabGroup.openTab(item: item, asTemporary: true) } } diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 7d0983a25c..e785dec62a 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -34,11 +34,8 @@ final class TabGroupData: ObservableObject, Identifiable { } } - @Published var history: Deque = [] { - didSet { - print(history.map(\.fileName)) - } - } + @Published var history: Deque = [] + @Published var historyOffset: Int = 0 { didSet { print("offset going to", historyOffset) @@ -127,6 +124,43 @@ final class TabGroupData: ObservableObject, Identifiable { // } } + var selectedIsTemporary = false + + func openTab(item: Tab, asTemporary: Bool, fromHistory: Bool = false) { + // Item is already opened in a tab. + guard !files.contains(item) || !asTemporary else { + print("Tab is already open") + selected = item + history.prepend(item) + return + } + + if let selected, let index = files.firstIndex(of: selected), asTemporary && selectedIsTemporary { + // Replace temporary tab + print("Replacing temporary tab") + history.prepend(item) + files.remove(selected) + files.insert(item, at: index) + self.selected = item + } else if selectedIsTemporary && !asTemporary { + // Temporary becomes permanent. + print("Selected became permanent") + openTab(item: item) + selectedIsTemporary = false + + } else { + // New temporary tab + print("New Temporary Tab") + openTab(item: item) + selectedIsTemporary = true + } + do { + try openFile(item: item) + } catch { + print(error) + } + } + func openTab(item: Tab, at index: Int? = nil, fromHistory: Bool = false) { if let index { files.insert(item, at: index) @@ -146,7 +180,7 @@ final class TabGroupData: ObservableObject, Identifiable { do { try openFile(item: item) } catch { - Swift.print(error) + print(error) } } @@ -165,7 +199,6 @@ final class TabGroupData: ObservableObject, Identifiable { CodeEditDocumentController.shared.addDocument(codeFile) Swift.print("Opening file for item: ", item.url) } - } extension TabGroupData: Equatable, Hashable { diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index 3e46159073..fe0916488a 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -72,7 +72,7 @@ struct TabBarItemView: View { var index: Int private var isTemporary: Bool { - false + isActive // TODO: Fix this // workspace.selectionState.temporaryTab == item.tabID } @@ -301,6 +301,7 @@ struct TabBarItemView: View { TapGesture(count: 2) .onEnded { _ in if isTemporary { + tabs.selectedIsTemporary = false // TODO: Fix this // workspace.convertTemporaryTab() } @@ -310,11 +311,11 @@ struct TabBarItemView: View { // This padding is to avoid background color overlapping with top divider. .top, prefs.preferences.general.tabBarStyle == .xcode ? 1 : 0 ) - .offset( - x: isAppeared || prefs.preferences.general.tabBarStyle == .native ? 0 : -14, - y: 0 - ) - .opacity(isAppeared && onDragTabId != item.id ? 1.0 : 0.0) +// .offset( +// x: isAppeared || prefs.preferences.general.tabBarStyle == .native ? 0 : -14, +// y: 0 +// ) +// .opacity(isAppeared && onDragTabId != item.id ? 1.0 : 0.0) .zIndex( isActive ? (prefs.preferences.general.tabBarStyle == .native ? -1 : 2) @@ -332,7 +333,7 @@ struct TabBarItemView: View { withAnimation( .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) ) { - isAppeared = true +// isAppeared = true } } .id(item.id) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index fd694c5a65..8f485c949e 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -321,6 +321,8 @@ struct TabBarView: View { onDragTabId: onDragTabId, closeButtonGestureActive: $closeButtonGestureActive ) + .transition(.asymmetric(insertion: .offset(x: -14).combined(with: .opacity), removal: .scale(scale: 0.7).combined(with: .opacity))) +// .animation(.spring()) .frame(height: TabBarView.height) .background(makeTabItemGeometryReader(id: id)) .offset(x: tabOffsets[id] ?? 0, y: 0) @@ -353,7 +355,13 @@ struct TabBarView: View { // On first tab appeared, jump to the corresponding position. scrollReader.scrollTo(tabs.selected) } + .onChange(of: tabs.files.count) { _ in + withAnimation(.easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20)) { + updateForTabCountChange(geometryProxy: geometryProxy) + } + } .onChange(of: tabs.files) { _ in + updateForTabCountChange(geometryProxy: geometryProxy) DispatchQueue.main.asyncAfter( deadline: .now() + .milliseconds( prefs.preferences.general.tabBarStyle == .native ? 150 : 200 @@ -368,9 +376,7 @@ struct TabBarView: View { scrollReader.scrollTo(selectedId) } // When tabs are changing, re-compute the expected tab width. - .onChange(of: tabs.files.count) { _ in - updateForTabCountChange(geometryProxy: geometryProxy) - } + // TODO: Fix this // .onChange(of: workspace.selectionState.temporaryTab, perform: { _ in // updateForTabCountChange(geometryProxy: geometryProxy) From 9a98b2f0560f47ab684d0671e828ddbe03d41204 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 14:58:02 +0100 Subject: [PATCH 44/82] Fixed force unwrap crash Signed-off-by: Wouter01 --- .../Features/Tabs/TabGroup/TabGroupData.swift | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index e785dec62a..6100b6308b 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -24,10 +24,8 @@ final class TabGroupData: ObservableObject, Identifiable { if let selected, change.contains(selected) { if let oldIndex = oldValue.firstIndex(of: selected), oldIndex - 1 < files.count, !files.isEmpty { self.selected = files[max(0, oldIndex-1)] - print("Selection becomes \(self.selected?.fileName)") } else { self.selected = nil - print("Selection becomes nil") } } } @@ -35,26 +33,17 @@ final class TabGroupData: ObservableObject, Identifiable { } @Published var history: Deque = [] - + @Published var historyOffset: Int = 0 { didSet { - print("offset going to", historyOffset) let tab = history[historyOffset] -// guard historyOffset != 0 else { -// selected = tab -// return -// } - if !files.contains(tab) { -// if let selected { openTab(item: tab, at: files.firstIndex(of: selected), fromHistory: true) } else { openTab(item: tab, fromHistory: true) } -// history.removeFirst() - } selected = tab } @@ -81,18 +70,14 @@ final class TabGroupData: ObservableObject, Identifiable { } func closeTab(item: Tab) { - print("Closing tab...") + historyOffset = 0 if item != selected { history.prepend(item) } files.remove(item) - history.prepend(selected!) - historyOffset = 0 - print("Closed tab.") -// history.removeFirst() - // if let selected { -// history.prepend(item) - // } + if let selected { + history.prepend(selected) + } guard let file = item.fileDocument else { return } @@ -113,8 +98,6 @@ final class TabGroupData: ObservableObject, Identifiable { } } - - // if openedTabsFromState { // var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] // if let index = openTabsInState.firstIndex(of: item.url.absoluteString) { From 0ad3190dcb79d5e0206a7541450c385daea06af5 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 15:01:03 +0100 Subject: [PATCH 45/82] removed prints Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 6100b6308b..f85f00df0c 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -112,7 +112,6 @@ final class TabGroupData: ObservableObject, Identifiable { func openTab(item: Tab, asTemporary: Bool, fromHistory: Bool = false) { // Item is already opened in a tab. guard !files.contains(item) || !asTemporary else { - print("Tab is already open") selected = item history.prepend(item) return @@ -120,20 +119,17 @@ final class TabGroupData: ObservableObject, Identifiable { if let selected, let index = files.firstIndex(of: selected), asTemporary && selectedIsTemporary { // Replace temporary tab - print("Replacing temporary tab") history.prepend(item) files.remove(selected) files.insert(item, at: index) self.selected = item } else if selectedIsTemporary && !asTemporary { // Temporary becomes permanent. - print("Selected became permanent") openTab(item: item) selectedIsTemporary = false } else { // New temporary tab - print("New Temporary Tab") openTab(item: item) selectedIsTemporary = true } @@ -151,11 +147,7 @@ final class TabGroupData: ObservableObject, Identifiable { files.append(item) } selected = item - if fromHistory { - print("Opening from history") - print(history.map(\.fileName)) -// historyOffset += 1 - } else { + if !fromHistory { history.removeFirst(historyOffset) history.prepend(item) historyOffset = 0 @@ -180,7 +172,7 @@ final class TabGroupData: ObservableObject, Identifiable { ) item.fileDocument = codeFile CodeEditDocumentController.shared.addDocument(codeFile) - Swift.print("Opening file for item: ", item.url) + print("Opening file for item: ", item.url) } } From 7c20ebcaa4f8ddafcaabc95e9100fe7a964e1420 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 16:29:14 +0100 Subject: [PATCH 46/82] Removed TODOs Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 4 - .../CodeEditWindowController.swift | 2 +- .../WorkspaceDocument+Selection.swift | 65 ---------------- .../Documents/WorkspaceDocument.swift | 28 ------- .../OutlineView/OutlineViewController.swift | 4 - .../SplitView/WorkspaceTabGroupView.swift | 4 + .../Features/Tabs/TabGroup/TabGroupData.swift | 7 +- .../Features/Tabs/Views/TabBarAccessory.swift | 2 +- .../Tabs/Views/TabBarContextMenu.swift | 2 +- .../Features/Tabs/Views/TabBarItemView.swift | 8 -- CodeEdit/Features/Tabs/Views/TabBarView.swift | 75 +++++++++++++------ 11 files changed, 65 insertions(+), 136 deletions(-) delete mode 100644 CodeEdit/Features/Documents/WorkspaceDocument+Selection.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index ea00a335c9..eea8ff0ebe 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 043BCF03281DA18A000AC47C /* WorkspaceDocument+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043BCF02281DA18A000AC47C /* WorkspaceDocument+Search.swift */; }; - 043BCF05281DA19A000AC47C /* WorkspaceDocument+Selection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043BCF04281DA19A000AC47C /* WorkspaceDocument+Selection.swift */; }; 043C321427E31FF6006AE443 /* CodeEditDocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */; }; 043C321627E3201F006AE443 /* WorkspaceDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043C321527E3201F006AE443 /* WorkspaceDocument.swift */; }; 043C321A27E32295006AE443 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 043C321927E32295006AE443 /* MainMenu.xib */; }; @@ -407,7 +406,6 @@ /* Begin PBXFileReference section */ 043BCF02281DA18A000AC47C /* WorkspaceDocument+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+Search.swift"; sourceTree = ""; }; - 043BCF04281DA19A000AC47C /* WorkspaceDocument+Selection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+Selection.swift"; sourceTree = ""; }; 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditDocumentController.swift; sourceTree = ""; }; 043C321527E3201F006AE443 /* WorkspaceDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceDocument.swift; sourceTree = ""; }; 043C321927E32295006AE443 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; @@ -806,7 +804,6 @@ 5831E3CF2933F4E000D5A6D2 /* Views */, 043C321527E3201F006AE443 /* WorkspaceDocument.swift */, 043BCF02281DA18A000AC47C /* WorkspaceDocument+Search.swift */, - 043BCF04281DA19A000AC47C /* WorkspaceDocument+Selection.swift */, 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */, ); path = Documents; @@ -2814,7 +2811,6 @@ 587B9E6129301D8F00AC7927 /* GitLabOAuthConfiguration.swift in Sources */, 587B9E6229301D8F00AC7927 /* GitLabConfiguration.swift in Sources */, 5879821B292D92370085B254 /* SearchResultMatchModel.swift in Sources */, - 043BCF05281DA19A000AC47C /* WorkspaceDocument+Selection.swift in Sources */, 587B9E5929301D8F00AC7927 /* GitCheckoutBranchView+CheckoutBranch.swift in Sources */, 58F2EB09292FB2B0004A9BDE /* TerminalPreferences.swift in Sources */, 587D9B742933BF5700BF7490 /* FileItem+Array.swift in Sources */, diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 54da9f72e5..6f194f87ed 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -249,7 +249,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { @IBAction func saveDocument(_ sender: Any) { getSelectedCodeFile()?.save(sender) - workspace?.convertTemporaryTab() + workspace?.tabManager.activeTabGroup.selectedIsTemporary = false } @IBAction func openCommandPalette(_ sender: Any) { diff --git a/CodeEdit/Features/Documents/WorkspaceDocument+Selection.swift b/CodeEdit/Features/Documents/WorkspaceDocument+Selection.swift deleted file mode 100644 index fb87eabd12..0000000000 --- a/CodeEdit/Features/Documents/WorkspaceDocument+Selection.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// WorkspaceDocument+Selection.swift -// CodeEdit -// -// Created by Pavel Kasila on 30.04.22. -// - -import Foundation - -struct WorkspaceSelectionState: Codable { - - var selectedId: TabBarItemID? - var openedTabs: [TabBarItemID] = [] - var temporaryTab: TabBarItemID? - var previousTemporaryTab: TabBarItemID? - - var selected: TabBarItemRepresentable? { - guard let selectedId = selectedId else { return nil } - return getItemByTab(id: selectedId) - } - - var openFileItems: [WorkspaceClient.FileItem] = [] - var openedCodeFiles: [WorkspaceClient.FileItem: CodeFileDocument] = [:] - - var openedExtensions: [Plugin] = [] - - enum CodingKeys: String, CodingKey { - case selectedId, openedTabs, temporaryTab, openedExtensions - } - - init() { - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - selectedId = try container.decode(TabBarItemID?.self, forKey: .selectedId) - openedTabs = try container.decode([TabBarItemID].self, forKey: .openedTabs) - temporaryTab = try container.decode(TabBarItemID?.self, forKey: .temporaryTab) - openedExtensions = try container.decode([Plugin].self, forKey: .openedExtensions) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(selectedId, forKey: .selectedId) - try container.encode(openedTabs, forKey: .openedTabs) - try container.encode(temporaryTab, forKey: .temporaryTab) - try container.encode(openedExtensions, forKey: .openedExtensions) - } - - /// Returns TabBarItemRepresentable by its identifier - /// - Parameter id: tab bar item's identifier - /// - Returns: item with passed identifier - func getItemByTab(id: TabBarItemID) -> TabBarItemRepresentable? { - switch id { - case .codeEditor: - return self.openFileItems.first { item in - item.tabID == id - } - case .extensionInstallation: - return self.openedExtensions.first { item in - item.tabID == id - } - } - } -} diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 203d50fff1..0fc4da86fe 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -63,26 +63,6 @@ import CodeEditKit workspaceState.updateValue(value, forKey: key) } - /// Makes the temporary tab permanent when a file save or edit happens. - @objc func convertTemporaryTab() { -// if selectionState.selectedId == selectionState.temporaryTab && -// selectionState.temporaryTab != nil { -// let item = selectionState.getItemByTab(id: selectionState.temporaryTab!) -// selectionState.previousTemporaryTab = selectionState.temporaryTab -// selectionState.temporaryTab = nil -// -// guard let file = item as? WorkspaceClient.FileItem else { return } -// -// if openedTabsFromState && item != nil { -// var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] -// if !openTabsInState.contains(file.url.absoluteString) { -// openTabsInState.append(file.url.absoluteString) -// self.addToWorkspaceState(key: openTabsStateName, value: openTabsInState) -// } -// } -// } - } - // MARK: NSDocument private let ignoredFilesAndDirectory = [ @@ -143,14 +123,6 @@ import CodeEditKit self.searchState = .init(self) self.quickOpenViewModel = .init(fileURL: url) self.commandsPaletteState = .init() - - // TODO: Fix temporary tabs -// NotificationCenter.default.addObserver( -// self, -// selector: #selector(convertTemporaryTab), -// name: NSNotification.Name("CodeEditor.didBeginEditing"), -// object: nil -// ) } override func read(from url: URL, ofType typeName: String) throws { diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift index d5a104ad5b..fa0737ae1b 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift @@ -110,11 +110,7 @@ final class OutlineViewController: NSViewController { outlineView.expandItem(item) } } else { - // TODO: Fix temporary tab workspace?.tabManager.activeTabGroup.openTab(item: item, asTemporary: false) -// if workspace?.selectionState.temporaryTab == item.tabID { -// workspace?.convertTemporaryTab() -// } } } diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index c27f61484b..8e2ad7672c 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -61,5 +61,9 @@ struct WorkspaceTabGroupView: View { tabManager.activeTabGroup = tabgroup } } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CodeEditor.didBeginEditing"))) { _ in + print("Not temporary anymore") + tabgroup.selectedIsTemporary = false + } } } diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index f85f00df0c..45968bc55a 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -36,6 +36,7 @@ final class TabGroupData: ObservableObject, Identifiable { @Published var historyOffset: Int = 0 { didSet { + let tab = history[historyOffset] if !files.contains(tab) { @@ -60,9 +61,10 @@ final class TabGroupData: ObservableObject, Identifiable { selected: Tab? = nil, parent: WorkspaceSplitViewData? = nil ) { - self.files = files - self.selected = selected ?? files.first + self.files = [] self.parent = parent + files.forEach { openTab(item: $0) } + self.selected = selected ?? files.first } func close() { @@ -125,7 +127,6 @@ final class TabGroupData: ObservableObject, Identifiable { self.selected = item } else if selectedIsTemporary && !asTemporary { // Temporary becomes permanent. - openTab(item: item) selectedIsTemporary = false } else { diff --git a/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift b/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift index 74f2c17eb0..d981e44749 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarAccessory.swift @@ -10,7 +10,7 @@ import SwiftUI /// Accessory icon's view for tab bar. struct TabBarAccessoryIcon: View { /// Unifies icon font for tab bar accessories. - private static let iconFont = Font.system(size: 14, weight: .regular, design: .default) + static let iconFont = Font.system(size: 14, weight: .regular, design: .default) private let icon: Image private let action: () -> Void diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index d9877289ca..720c905d93 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -88,7 +88,7 @@ struct TabBarContextMenu: ViewModifier { if isTemporary { Button("Keep Open") { - workspace.convertTemporaryTab() + tabs.selectedIsTemporary = false } } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index fe0916488a..1ab831ee18 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -73,8 +73,6 @@ struct TabBarItemView: View { private var isTemporary: Bool { isActive - // TODO: Fix this - // workspace.selectionState.temporaryTab == item.tabID } /// Is the current tab the active tab. @@ -103,10 +101,6 @@ struct TabBarItemView: View { tabs.history.removeFirst(tabs.historyOffset) tabs.history.prepend(item) tabs.historyOffset = 0 - - // if workspace.selectionState.selectedId != item.tabID { - // workspace.switchedTab(item: item) - // TODO: Fix save state } } @@ -302,8 +296,6 @@ struct TabBarItemView: View { .onEnded { _ in if isTemporary { tabs.selectedIsTemporary = false - // TODO: Fix this - // workspace.convertTemporaryTab() } } ) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 8f485c949e..e5cc010986 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -375,12 +375,7 @@ struct TabBarView: View { guard let selectedId = targetId else { return } scrollReader.scrollTo(selectedId) } - // When tabs are changing, re-compute the expected tab width. - // TODO: Fix this - // .onChange(of: workspace.selectionState.temporaryTab, perform: { _ in - // updateForTabCountChange(geometryProxy: geometryProxy) - // }) // When window size changes, re-compute the expected tab width. .onChange(of: geometryProxy.size.width) { _ in updateExpectedTabWidth(proxy: geometryProxy) @@ -396,12 +391,7 @@ struct TabBarView: View { } .frame(height: TabBarView.height) } - // When there is no opened file, hide the scroll view, but keep the background. - .opacity( - tabs.files.isEmpty // TODO: Fix this && workspace.selectionState.temporaryTab == nil - ? 0.0 - : 1.0 - ) + // To fill up the parent space of tab bar. .frame(maxWidth: .infinity) .background { @@ -454,17 +444,60 @@ struct TabBarView: View { .padding(.horizontal, 4) } - TabBarAccessoryIcon(icon: .init(systemName: "chevron.left")) { - tabs.historyOffset += 1 - } - .disabled(tabs.historyOffset == tabs.history.count-1 || tabs.history.isEmpty) - .help("Navigate back") - - TabBarAccessoryIcon(icon: .init(systemName: "chevron.right")) { - tabs.historyOffset -= 1 + Group { + Menu { + ForEach( + Array(tabs.history.dropFirst(tabs.historyOffset+1).enumerated()), + id: \.element + ) { index, tab in + Button { + tabs.historyOffset += index + 1 + } label: { + HStack { + tab.icon + Text(tab.fileName) + } + } + } + } label: { + Image(systemName: "chevron.left") + .controlSize(.regular) + .brightness(tabs.historyOffset == tabs.history.count-1 || tabs.history.isEmpty ? -0.4 : 0.0) + } primaryAction: { + tabs.historyOffset += 1 + } + .disabled(tabs.historyOffset == tabs.history.count-1 || tabs.history.isEmpty) + .help("Navigate back") + + Menu { + ForEach( + Array(tabs.history.prefix(tabs.historyOffset).reversed().enumerated()), + id: \.element + ) { index, tab in + Button { + tabs.historyOffset -= index + 1 + } label: { + HStack { + tab.icon + Text(tab.fileName) + } + } + } + } label: { + Image(systemName: "chevron.right") + .controlSize(.regular) + .brightness(tabs.historyOffset == 0 ? -0.4 : 0.0) + } primaryAction: { + tabs.historyOffset -= 1 + } + .disabled(tabs.historyOffset == 0) + .help("Navigate forward") } - .disabled(tabs.historyOffset == 0) - .help("Navigate forward") + .controlSize(.small) + .font(TabBarAccessoryIcon.iconFont) + .frame(height: TabBarView.height - 2) + .padding(.horizontal, 4) + .contentShape(Rectangle()) } .foregroundColor(.secondary) .buttonStyle(.plain) From 0dbe98f613b1bbb89d8763f70ab33fceb474f543 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 16:48:59 +0100 Subject: [PATCH 47/82] fixed warnings Signed-off-by: Wouter01 --- .../Controllers/CodeEditWindowController.swift | 8 +++----- .../Features/Documents/WorkspaceDocument.swift | 6 +++++- .../Keybindings/ModifierKeysObserver.swift | 14 -------------- CodeEdit/Features/SplitView/EditorSplitView.swift | 10 +++++++--- .../StatusBar/ViewModels/StatusBarViewModel.swift | 2 +- CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift | 1 - CodeEdit/Features/Tabs/Views/TabBarView.swift | 14 ++++++++++---- CodeEdit/WindowObserver.swift | 6 ++++++ 8 files changed, 32 insertions(+), 29 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 6f194f87ed..ad58b4a612 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -83,11 +83,9 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { splitVC.addSplitViewItem(navigator) let workspaceView = WindowObserver(window: window!) { - EventModifierObserver { - WorkspaceView() - .environmentObject(workspace) - .environmentObject(workspace.tabManager) - } + WorkspaceView() + .environmentObject(workspace) + .environmentObject(workspace.tabManager) } let mainContent = NSSplitViewItem( diff --git a/CodeEdit/Features/Documents/WorkspaceDocument.swift b/CodeEdit/Features/Documents/WorkspaceDocument.swift index 0fc4da86fe..47ec07c00d 100644 --- a/CodeEdit/Features/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Features/Documents/WorkspaceDocument.swift @@ -199,7 +199,11 @@ import CodeEditKit return } // Save unsaved changes before closing - let editedCodeFiles = tabManager.tabGroups.gatherOpenFiles().compactMap(\.fileDocument).filter(\.isDocumentEdited) + let editedCodeFiles = tabManager.tabGroups + .gatherOpenFiles() + .compactMap(\.fileDocument) + .filter(\.isDocumentEdited) + for editedCodeFile in editedCodeFiles { let shouldClose = UnsafeMutablePointer.allocate(capacity: 1) shouldClose.initialize(to: true) diff --git a/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift b/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift index 9d336e24c8..8a38875c1e 100644 --- a/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift +++ b/CodeEdit/Features/Keybindings/ModifierKeysObserver.swift @@ -8,20 +8,6 @@ import SwiftUI import Combine -struct EventModifierObserver: View { - @ViewBuilder var content: Content - - @State var modifierFlags: NSEvent.ModifierFlags = [] - - var body: some View { - content - .environment(\.modifierKeys, modifierFlags.intersection(.deviceIndependentFlagsMask)) - .onReceive(NSEvent.publisher(scope: .local, matching: .flagsChanged)) { output in - modifierFlags = output.modifierFlags - } - } -} - struct EventModifierEnvironmentKey: EnvironmentKey { static var defaultValue: NSEvent.ModifierFlags = [] } diff --git a/CodeEdit/Features/SplitView/EditorSplitView.swift b/CodeEdit/Features/SplitView/EditorSplitView.swift index 39dc3af71a..aa37f7343b 100644 --- a/CodeEdit/Features/SplitView/EditorSplitView.swift +++ b/CodeEdit/Features/SplitView/EditorSplitView.swift @@ -77,15 +77,19 @@ struct EditorSplitView: NSViewControllerRepresentable { controller.splitViewItems = controller.items.map(\.item) if hasChanged && controller.splitViewItems.count > 1 { - let numerator = controller.splitView.isVertical ? controller.splitView.frame.width : controller.splitView.frame.height + let splitView = controller.splitView + let numerator = splitView.isVertical ? splitView.frame.width : splitView.frame.height for idx in 0..: View { @StateObject private var prefs: AppPreferencesModel = .shared + @State var modifierFlags: NSEvent.ModifierFlags = [] + var body: some View { content + .environment(\.modifierKeys, modifierFlags.intersection(.deviceIndependentFlagsMask)) + .onReceive(NSEvent.publisher(scope: .local, matching: .flagsChanged)) { output in + modifierFlags = output.modifierFlags + } .environment(\.window, window) .environment(\.isFullscreen, isFullscreen) .onReceive(NotificationCenter.default.publisher(for: NSWindow.didEnterFullScreenNotification)) { _ in From de9176492b3300bf18fab8deea168b0538f3fe28 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 17:01:14 +0100 Subject: [PATCH 48/82] Added docs Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/TabManager.swift | 34 ++++----------- .../SplitView/WorkspaceTabGroupView.swift | 2 +- .../Features/Tabs/TabGroup/TabGroup.swift | 6 ++- .../Features/Tabs/TabGroup/TabGroupData.swift | 43 +++++++++++-------- .../Tabs/Views/TabBarContextMenu.swift | 10 ++--- CodeEdit/Features/Tabs/Views/TabBarView.swift | 18 ++++---- 6 files changed, 53 insertions(+), 60 deletions(-) diff --git a/CodeEdit/Features/SplitView/TabManager.swift b/CodeEdit/Features/SplitView/TabManager.swift index eaff971eed..4892804947 100644 --- a/CodeEdit/Features/SplitView/TabManager.swift +++ b/CodeEdit/Features/SplitView/TabManager.swift @@ -9,49 +9,31 @@ import Foundation import OrderedCollections class TabManager: ObservableObject { + /// Collection of all the tabgroups. @Published var tabGroups: TabGroup + /// The TabGroup with active focus. @Published var activeTabGroup: TabGroupData { didSet { - activeTabHistory.updateOrInsert(oldValue, at: 0) + activeTabGroupHistory.updateOrInsert(oldValue, at: 0) } } - var activeTabHistory: OrderedSet = [] + /// History of last-used tab groups. + var activeTabGroupHistory: OrderedSet = [] var fileDocuments: [WorkspaceClient.FileItem: CodeFileDocument] = [:] init() { let tab = TabGroupData() self.activeTabGroup = tab - self.activeTabHistory.append(tab) + self.activeTabGroupHistory.append(tab) self.tabGroups = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) } + /// Opens a new tab in a tabgroup. If no tabgroup is given, it is added to the active tab group. func openTab(item: WorkspaceClient.FileItem, in tabgroup: TabGroupData? = nil) { let tabgroup = tabgroup ?? activeTabGroup - tabgroup.files.append(item) - tabgroup.selected = item - do { - try openFile(item: item) - } catch { - print(error) - } - } - - private func openFile(item: WorkspaceClient.FileItem) throws { - guard item.fileDocument == nil else { - return - } - - let contentType = try item.url.resourceValues(forKeys: [.contentTypeKey]).contentType - let codeFile = try CodeFileDocument( - for: item.url, - withContentsOf: item.url, - ofType: contentType?.identifier ?? "" - ) - item.fileDocument = codeFile - CodeEditDocumentController.shared.addDocument(codeFile) - Swift.print("Opening file for item: ", item.url) + tabgroup.openTab(item: item) } } diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift index 8e2ad7672c..ffdc4ed7a7 100644 --- a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift @@ -44,7 +44,7 @@ struct WorkspaceTabGroupView: View { Divider() if let file = tabgroup.selected { BreadcrumbsView(file: file) { newFile in - let index = tabgroup.files.firstIndex(of: file) + let index = tabgroup.tabs.firstIndex(of: file) if let index { tabgroup.openTab(item: newFile, at: index) } diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift index 0e462818b5..cdbb2b34d2 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift @@ -15,7 +15,7 @@ enum TabGroup { func closeAllTabs(of file: WorkspaceClient.FileItem) { switch self { case .one(let tabGroupData): - tabGroupData.files.remove(file) + tabGroupData.tabs.remove(file) case .vertical(let data), .horizontal(let data): data.tabgroups.forEach { $0.closeAllTabs(of: file) @@ -23,6 +23,7 @@ enum TabGroup { } } + /// Returns some tabgroup, except the given tabgroup. func findSomeTabGroup(except: TabGroupData? = nil) -> TabGroupData? { switch self { case .one(let tabGroupData) where tabGroupData != except: @@ -39,10 +40,11 @@ enum TabGroup { } } + /// Forms a set of all files currently represented by tabs. func gatherOpenFiles() -> Set { switch self { case .one(let tabGroupData): - return Set(tabGroupData.files) + return Set(tabGroupData.tabs) case .vertical(let data), .horizontal(let data): return data.tabgroups.map { $0.gatherOpenFiles() }.reduce(into: []) { $0.formUnion($1) } } diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index e69a6ef6b9..285f6a70d1 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -12,18 +12,19 @@ import DequeModule final class TabGroupData: ObservableObject, Identifiable { typealias Tab = WorkspaceClient.FileItem - @Published var files: OrderedSet = [] { + /// Set of open tabs. + @Published var tabs: OrderedSet = [] { didSet { - let change = files.symmetricDifference(oldValue) + let change = tabs.symmetricDifference(oldValue) - if files.count > oldValue.count { + if tabs.count > oldValue.count { // Amount of tabs grew, so set the first new as selected. selected = change.first } else { // Selected file was removed if let selected, change.contains(selected) { - if let oldIndex = oldValue.firstIndex(of: selected), oldIndex - 1 < files.count, !files.isEmpty { - self.selected = files[max(0, oldIndex-1)] + if let oldIndex = oldValue.firstIndex(of: selected), oldIndex - 1 < tabs.count, !tabs.isEmpty { + self.selected = tabs[max(0, oldIndex-1)] } else { self.selected = nil } @@ -32,15 +33,17 @@ final class TabGroupData: ObservableObject, Identifiable { } } + /// History of tab switching. @Published var history: Deque = [] + /// The current offset in the history list. @Published var historyOffset: Int = 0 { didSet { let tab = history[historyOffset] - if !files.contains(tab) { + if !tabs.contains(tab) { if let selected { - openTab(item: tab, at: files.firstIndex(of: selected), fromHistory: true) + openTab(item: tab, at: tabs.firstIndex(of: selected), fromHistory: true) } else { openTab(item: tab, fromHistory: true) } @@ -49,8 +52,11 @@ final class TabGroupData: ObservableObject, Identifiable { } } + /// Currently selected tab. @Published var selected: Tab? + var selectedIsTemporary = false + let id = UUID() weak var parent: WorkspaceSplitViewData? @@ -60,22 +66,24 @@ final class TabGroupData: ObservableObject, Identifiable { selected: Tab? = nil, parent: WorkspaceSplitViewData? = nil ) { - self.files = [] + self.tabs = [] self.parent = parent files.forEach { openTab(item: $0) } self.selected = selected ?? files.first } + /// Closes the tabgroup. func close() { parent?.closeTabGroup(with: id) } + /// Closes a tab in the tabgroup. func closeTab(item: Tab) { historyOffset = 0 if item != selected { history.prepend(item) } - files.remove(item) + tabs.remove(item) if let selected { history.prepend(selected) } @@ -108,21 +116,21 @@ final class TabGroupData: ObservableObject, Identifiable { // } } - var selectedIsTemporary = false - + /// Opens a tab in the tabgroup. + /// If a tab for the item already exists, it is used instead. func openTab(item: Tab, asTemporary: Bool, fromHistory: Bool = false) { // Item is already opened in a tab. - guard !files.contains(item) || !asTemporary else { + guard !tabs.contains(item) || !asTemporary else { selected = item history.prepend(item) return } - if let selected, let index = files.firstIndex(of: selected), asTemporary && selectedIsTemporary { + if let selected, let index = tabs.firstIndex(of: selected), asTemporary && selectedIsTemporary { // Replace temporary tab history.prepend(item) - files.remove(selected) - files.insert(item, at: index) + tabs.remove(selected) + tabs.insert(item, at: index) self.selected = item } else if selectedIsTemporary && !asTemporary { // Temporary becomes permanent. @@ -140,11 +148,12 @@ final class TabGroupData: ObservableObject, Identifiable { } } + /// Opens a tab in the tabgroup. func openTab(item: Tab, at index: Int? = nil, fromHistory: Bool = false) { if let index { - files.insert(item, at: index) + tabs.insert(item, at: index) } else { - files.append(item) + tabs.append(item) } selected = item if !fromHistory { diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index 720c905d93..46324542dc 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -59,7 +59,7 @@ struct TabBarContextMenu: ViewModifier { Button("Close Other Tabs") { withAnimation { - tabs.files.forEach { file in + tabs.tabs.forEach { file in if file != item { tabs.closeTab(item: file) } @@ -68,19 +68,19 @@ struct TabBarContextMenu: ViewModifier { } Button("Close Tabs to the Right") { withAnimation { - if let index = tabs.files.firstIndex(of: item) { - tabs.files[index...].forEach { + if let index = tabs.tabs.firstIndex(of: item) { + tabs.tabs[index...].forEach { tabs.closeTab(item: $0) } } } } // Disable this option when current tab is the last one. - .disabled(tabs.files.last == item) + .disabled(tabs.tabs.last == item) Button("Close All") { withAnimation { - tabs.files.forEach { + tabs.tabs.forEach { tabs.closeTab(item: $0) } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index eae94eda1b..68826de32e 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -135,7 +135,7 @@ struct TabBarView: View { private func updateExpectedTabWidth(proxy: GeometryProxy) { expectedTabWidth = max( // Equally divided size of a native tab. - (proxy.size.width + 1) / CGFloat(tabs.files.count) + 1, + (proxy.size.width + 1) / CGFloat(tabs.tabs.count) + 1, // Min size of a native tab. CGFloat(140) ) @@ -249,8 +249,8 @@ struct TabBarView: View { // In order to avoid the lag due to the update of workspace state. DispatchQueue.main.asyncAfter(deadline: .now() + 0.40) { if draggingStartLocation == nil { - tabs.files = .init(openedTabs.compactMap { id in - tabs.files.first { $0.id == id } + tabs.tabs = .init(openedTabs.compactMap { id in + tabs.tabs.first { $0.id == id } }) // workspace.reorderedTabs(openedTabs: openedTabs) // TODO: Fix save state @@ -288,7 +288,7 @@ struct TabBarView: View { /// Called when the tab count changes or the temporary tab changes. /// - Parameter geometryProxy: The geometry proxy to calculate the new width using. private func updateForTabCountChange(geometryProxy: GeometryProxy) { - openedTabs = tabs.files.map(\.id) + openedTabs = tabs.tabs.map(\.id) // Only update the expected width when user is not hovering over tabs. // This should give users a better experience on closing multiple tabs continuously. @@ -312,7 +312,7 @@ struct TabBarView: View { spacing: -1 // Negative spacing for overlapping the divider. ) { ForEach(Array(openedTabs.enumerated()), id: \.element) { index, id in - if let item = tabs.files.first(where: { $0.id == id }) { + if let item = tabs.tabs.first(where: { $0.id == id }) { TabBarItemView( expectedWidth: expectedTabWidth, item: item, @@ -353,20 +353,20 @@ struct TabBarView: View { // This padding is to hide dividers at two ends under the accessory view divider. .padding(.horizontal, prefs.preferences.general.tabBarStyle == .native ? -1 : 0) .onAppear { - openedTabs = tabs.files.map(\.id) + openedTabs = tabs.tabs.map(\.id) // On view appeared, compute the initial expected width for tabs. updateExpectedTabWidth(proxy: geometryProxy) // On first tab appeared, jump to the corresponding position. scrollReader.scrollTo(tabs.selected) } - .onChange(of: tabs.files.count) { _ in + .onChange(of: tabs.tabs.count) { _ in withAnimation( .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) ) { updateForTabCountChange(geometryProxy: geometryProxy) } } - .onChange(of: tabs.files) { _ in + .onChange(of: tabs.tabs) { _ in updateForTabCountChange(geometryProxy: geometryProxy) DispatchQueue.main.asyncAfter( deadline: .now() + .milliseconds( @@ -439,7 +439,7 @@ struct TabBarView: View { action: { tabs.close() if tabManager.activeTabGroup == tabs { - tabManager.activeTabGroup = tabManager.activeTabHistory.removeFirst() + tabManager.activeTabGroup = tabManager.activeTabGroupHistory.removeFirst() } } ) From 2cd5670cb1b4922e900475671d50840b77a72d18 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 17:08:53 +0100 Subject: [PATCH 49/82] Reorganised some files Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 28 ++++-- CodeEdit/Features/SplitView/SplitView.swift | 97 +------------------ ...ew.swift => SplitViewControllerView.swift} | 45 +-------- .../Features/SplitView/SplitViewItem.swift | 47 +++++++++ .../SplitView/SplitViewModifiers.swift | 37 +++++++ .../Features/SplitView/SplitViewReader.swift | 51 ++++++++++ CodeEdit/Features/SplitView/Variadic.swift | 23 +++++ .../Models}/TabManager.swift | 0 .../TabGroup/WorkspaceSplitViewData.swift | 2 +- .../TabGroup}/WorkspaceTabGroupView.swift | 0 .../Tabs/Views/TabBarContextMenu.swift | 36 ++++--- .../WorkspaceClient/Model/FileItem.swift | 1 + 12 files changed, 201 insertions(+), 166 deletions(-) rename CodeEdit/Features/SplitView/{EditorSplitView.swift => SplitViewControllerView.swift} (66%) create mode 100644 CodeEdit/Features/SplitView/SplitViewItem.swift create mode 100644 CodeEdit/Features/SplitView/SplitViewModifiers.swift create mode 100644 CodeEdit/Features/SplitView/SplitViewReader.swift create mode 100644 CodeEdit/Features/SplitView/Variadic.swift rename CodeEdit/Features/{SplitView => Tabs/Models}/TabManager.swift (100%) rename CodeEdit/Features/{SplitView => Tabs/TabGroup}/WorkspaceTabGroupView.swift (100%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index eea8ff0ebe..fc1b5834db 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -314,6 +314,10 @@ 6C14CEB028777D3C001468FE /* FindNavigatorListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */; }; 6C14CEB32877A68F001468FE /* FindNavigatorMatchListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */; }; 6C18620A298BF5A800C663EA /* RecentProjectsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C186209298BF5A800C663EA /* RecentProjectsListView.swift */; }; + 6C2C155829B4F49100EA60A5 /* SplitViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C155729B4F49100EA60A5 /* SplitViewItem.swift */; }; + 6C2C155A29B4F4CC00EA60A5 /* Variadic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C155929B4F4CC00EA60A5 /* Variadic.swift */; }; + 6C2C155D29B4F4E500EA60A5 /* SplitViewReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C155C29B4F4E500EA60A5 /* SplitViewReader.swift */; }; + 6C2C156129B4F52F00EA60A5 /* SplitViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2C156029B4F52F00EA60A5 /* SplitViewModifiers.swift */; }; 6C4104E3297C87A000F472BA /* BlurButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E2297C87A000F472BA /* BlurButtonStyle.swift */; }; 6C4104E6297C884F00F472BA /* AboutDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E5297C884F00F472BA /* AboutDetailView.swift */; }; 6C4104E9297C970F00F472BA /* AboutDefaultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */; }; @@ -322,7 +326,7 @@ 6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */; }; 6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */; }; 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; }; - 6C7256D729A3D7D000C2D3E0 /* EditorSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */; }; + 6C7256D729A3D7D000C2D3E0 /* SplitViewControllerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* SplitViewControllerView.swift */; }; 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */; }; 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */; }; 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 6C81916A29B41DD300B75C92 /* DequeModule */; }; @@ -709,6 +713,10 @@ 6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorListViewController.swift; sourceTree = ""; }; 6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorMatchListCell.swift; sourceTree = ""; }; 6C186209298BF5A800C663EA /* RecentProjectsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentProjectsListView.swift; sourceTree = ""; }; + 6C2C155729B4F49100EA60A5 /* SplitViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewItem.swift; sourceTree = ""; }; + 6C2C155929B4F4CC00EA60A5 /* Variadic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Variadic.swift; sourceTree = ""; }; + 6C2C155C29B4F4E500EA60A5 /* SplitViewReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewReader.swift; sourceTree = ""; }; + 6C2C156029B4F52F00EA60A5 /* SplitViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewModifiers.swift; sourceTree = ""; }; 6C4104E2297C87A000F472BA /* BlurButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurButtonStyle.swift; sourceTree = ""; }; 6C4104E5297C884F00F472BA /* AboutDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDetailView.swift; sourceTree = ""; }; 6C4104E8297C970F00F472BA /* AboutDefaultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutDefaultView.swift; sourceTree = ""; }; @@ -717,7 +725,7 @@ 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = ""; }; 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ContentInsets.swift"; sourceTree = ""; }; 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; - 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorSplitView.swift; sourceTree = ""; }; + 6C7256D629A3D7D000C2D3E0 /* SplitViewControllerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewControllerView.swift; sourceTree = ""; }; 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifierKeysObserver.swift; sourceTree = ""; }; 6C91D57129B176FF0059A90D /* TabManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManager.swift; sourceTree = ""; }; 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; @@ -1851,6 +1859,7 @@ 58AFAA272933C65C00482B53 /* Models */ = { isa = PBXGroup; children = ( + 6C91D57129B176FF0059A90D /* TabManager.swift */, 58AFAA2D2933C69E00482B53 /* TabBarItemID.swift */, 58AFAA2C2933C69E00482B53 /* TabBarItemRepresentable.swift */, ); @@ -2190,6 +2199,7 @@ 6C147C3C29A328020089B630 /* TabGroup */ = { isa = PBXGroup; children = ( + 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */, 6C147C3F29A328560089B630 /* WorkspaceSplitViewData.swift */, 6C147C3E29A3281D0089B630 /* TabGroup.swift */, 6C147C3D29A3281D0089B630 /* TabGroupData.swift */, @@ -2200,13 +2210,15 @@ 6C147C4729A329E50089B630 /* SplitView */ = { isa = PBXGroup; children = ( - 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */, 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */, 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */, 6C147C4829A32A080089B630 /* EditorView.swift */, 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */, - 6C7256D629A3D7D000C2D3E0 /* EditorSplitView.swift */, - 6C91D57129B176FF0059A90D /* TabManager.swift */, + 6C2C156029B4F52F00EA60A5 /* SplitViewModifiers.swift */, + 6C2C155C29B4F4E500EA60A5 /* SplitViewReader.swift */, + 6C2C155929B4F4CC00EA60A5 /* Variadic.swift */, + 6C7256D629A3D7D000C2D3E0 /* SplitViewControllerView.swift */, + 6C2C155729B4F49100EA60A5 /* SplitViewItem.swift */, ); path = SplitView; sourceTree = ""; @@ -2639,7 +2651,7 @@ 58F2EAF4292FB2B0004A9BDE /* ThemeModel.swift in Sources */, 58F2EB0D292FB2B0004A9BDE /* ThemePreferences.swift in Sources */, 587B9D9F29300ABD00AC7927 /* SegmentedControl.swift in Sources */, - 6C7256D729A3D7D000C2D3E0 /* EditorSplitView.swift in Sources */, + 6C7256D729A3D7D000C2D3E0 /* SplitViewControllerView.swift in Sources */, 58FD7609291EA1CB0051D6E4 /* CommandPaletteView.swift in Sources */, 58F2EAFC292FB2B0004A9BDE /* GitAccountItemView.swift in Sources */, 587B9E8F29301D8F00AC7927 /* BitBucketUserRouter.swift in Sources */, @@ -2667,6 +2679,7 @@ 58798234292E30B90085B254 /* FeedbackIssueArea.swift in Sources */, 587B9E5F29301D8F00AC7927 /* GitLabProjectRouter.swift in Sources */, 587B9E7329301D8F00AC7927 /* GitRouter.swift in Sources */, + 6C2C156129B4F52F00EA60A5 /* SplitViewModifiers.swift in Sources */, 0463E51327FCC1FB00806D5C /* CodeEditTargetsAPI.swift in Sources */, 201169DD2837B3AC00F92B46 /* SourceControlToolbarBottom.swift in Sources */, 04C3254D27FF331B00C8DA2D /* ExtensionNavigatorItemView.swift in Sources */, @@ -2706,6 +2719,7 @@ 587B9E7529301D8F00AC7927 /* String+QueryParameters.swift in Sources */, 58798219292D92370085B254 /* SearchModeModel.swift in Sources */, 58D01C9D293167DC00C5B6B4 /* KeychainSwiftAccessOptions.swift in Sources */, + 6C2C155A29B4F4CC00EA60A5 /* Variadic.swift in Sources */, 5882252B292C280D00E83CDE /* StatusBarCursorLocationLabel.swift in Sources */, 587B9D57292FC27A00AC7927 /* FolderMonitor.swift in Sources */, 58798252292E78D80085B254 /* ImageFileView.swift in Sources */, @@ -2874,6 +2888,7 @@ 58822525292C280D00E83CDE /* StatusBarMenuLabel.swift in Sources */, 58F2EB11292FB2B0004A9BDE /* PreferencesToolbar.swift in Sources */, 6C147C4229A328C10089B630 /* TabGroupData.swift in Sources */, + 6C2C155829B4F49100EA60A5 /* SplitViewItem.swift in Sources */, 58F2EAF0292FB2B0004A9BDE /* PreviewThemeView.swift in Sources */, 58F2EAFF292FB2B0004A9BDE /* KeybindingsPreferencesView.swift in Sources */, 6CDA84AD284C1BA000C1CC3A /* TabBarContextMenu.swift in Sources */, @@ -2890,6 +2905,7 @@ 6C05A8AF284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift in Sources */, 587D9B732933BF5700BF7490 /* FileIcon.swift in Sources */, 58F2EAF9292FB2B0004A9BDE /* AccountSelectionDialog.swift in Sources */, + 6C2C155D29B4F4E500EA60A5 /* SplitViewReader.swift in Sources */, 58F2EAE8292FB2B0004A9BDE /* TerminalPreferencesView.swift in Sources */, 58AFAA2F2933C69E00482B53 /* TabBarItemID.swift in Sources */, 28FFE1BF27E3A441001939DB /* NavigatorSidebarToolbarBottom.swift in Sources */, diff --git a/CodeEdit/Features/SplitView/SplitView.swift b/CodeEdit/Features/SplitView/SplitView.swift index 68b36e017e..6655d1b061 100644 --- a/CodeEdit/Features/SplitView/SplitView.swift +++ b/CodeEdit/Features/SplitView/SplitView.swift @@ -7,21 +7,6 @@ import SwiftUI -// swiftlint:disable identifier_name -struct Helper: _VariadicView_UnaryViewRoot { - var _body: (_VariadicView.Children) -> Result - - func body(children: _VariadicView.Children) -> some View { - _body(children) - } -} - -extension View { - func variadic(@ViewBuilder process: @escaping (_VariadicView.Children) -> R) -> some View { - _VariadicView.Tree(Helper(_body: process), content: { self }) - } -} - struct SplitView: View { var content: Content @@ -36,89 +21,9 @@ struct SplitView: View { var body: some View { VStack { content.variadic { children in - EditorSplitView(children: children, viewController: viewController) + SplitViewControllerView(children: children, viewController: viewController) } } ._trait(SplitViewControllerLayoutValueKey.self, viewController) } } - -struct SplitViewReader: View { - - @ViewBuilder var content: (SplitViewProxy) -> Content - - @State private var viewController: SplitViewController? - - private var proxy: SplitViewProxy { - .init { - viewController - } - } - - var body: some View { - content(proxy) - .variadic { children in - ForEach(children, id: \.id) { child in - child - .task { - if let vc = child[SplitViewControllerLayoutValueKey.self] { - viewController = vc - } - } - } - } - } -} - -struct SplitViewProxy { - private var viewController: () -> SplitViewController? - - fileprivate init(viewController: @escaping () -> SplitViewController?) { - self.viewController = viewController - } - - func setPosition(of index: Int, position: CGFloat) { - viewController()?.splitView.setPosition(position, ofDividerAt: index) - } - - func collapseView(with id: AnyHashable, _ enabled: Bool) { - viewController()?.collapse(for: id, enabled: enabled) - } -} - -struct SplitViewControllerLayoutValueKey: _ViewTraitKey { - static var defaultValue: SplitViewController? -} - -struct SplitViewItemCollapsedViewTraitKey: _ViewTraitKey { - static var defaultValue: Binding = .constant(false) -} - -struct SplitViewItemCanCollapseViewTraitKey: _ViewTraitKey { - static var defaultValue: Bool = false -} - -struct SplitViewItemMinimumHeightViewTraitKey: _ViewTraitKey { - static var defaultValue: Bool = false -} - -struct SplitViewItemMaximumHeightViewTraitKey: _ViewTraitKey { - static var defaultValue: Bool = false -} - -extension View { - func collapsed(_ value: Binding) -> some View { - self - // Use get/set instead of binding directly, so a view update will be triggered if the binding changes. - ._trait(SplitViewItemCollapsedViewTraitKey.self, .init { - value.wrappedValue - } set: { - value.wrappedValue = $0 - }) - } - - func collapsable() -> some View { - self - ._trait(SplitViewItemCanCollapseViewTraitKey.self, true) - } -} diff --git a/CodeEdit/Features/SplitView/EditorSplitView.swift b/CodeEdit/Features/SplitView/SplitViewControllerView.swift similarity index 66% rename from CodeEdit/Features/SplitView/EditorSplitView.swift rename to CodeEdit/Features/SplitView/SplitViewControllerView.swift index aa37f7343b..2cc909ec93 100644 --- a/CodeEdit/Features/SplitView/EditorSplitView.swift +++ b/CodeEdit/Features/SplitView/SplitViewControllerView.swift @@ -6,47 +6,8 @@ // import SwiftUI -import Combine -class SplitViewItem: ObservableObject { - - var id: AnyHashable - var item: NSSplitViewItem - - var collapsed: Binding - - var cancellables: [AnyCancellable] = [] - - var observers: [NSKeyValueObservation] = [] - - init(child: _VariadicView.Children.Element) { - self.id = child.id - self.item = NSSplitViewItem(viewController: NSHostingController(rootView: child)) - self.collapsed = child[SplitViewItemCollapsedViewTraitKey.self] - self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] - self.item.isCollapsed = self.collapsed.wrappedValue - self.observers = createObservers() - } - - func createObservers() -> [NSKeyValueObservation] { - [ - item.observe(\.isCollapsed) { item, _ in - self.collapsed.wrappedValue = item.isCollapsed - } - ] - } - - func update(child: _VariadicView.Children.Element) { - self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] - DispatchQueue.main.async { - self.observers = [] - self.item.animator().isCollapsed = child[SplitViewItemCollapsedViewTraitKey.self].wrappedValue - self.observers = self.createObservers() - } - } -} - -struct EditorSplitView: NSViewControllerRepresentable { +struct SplitViewControllerView: NSViewControllerRepresentable { var children: _VariadicView.Children var viewController: SplitViewController @@ -125,8 +86,4 @@ final class SplitViewController: NSSplitViewController { func collapse(for id: AnyHashable, enabled: Bool) { items.first { $0.id == id }?.item.animator().isCollapsed = enabled } - -// override func splitViewDidResizeSubviews(_ notification: Notification) { -// print(notification) -// } } diff --git a/CodeEdit/Features/SplitView/SplitViewItem.swift b/CodeEdit/Features/SplitView/SplitViewItem.swift new file mode 100644 index 0000000000..370c332916 --- /dev/null +++ b/CodeEdit/Features/SplitView/SplitViewItem.swift @@ -0,0 +1,47 @@ +// +// SplitViewItem.swift +// CodeEdit +// +// Created by Wouter Hennen on 05/03/2023. +// + +import SwiftUI +import Combine + +class SplitViewItem: ObservableObject { + + var id: AnyHashable + var item: NSSplitViewItem + + var collapsed: Binding + + var cancellables: [AnyCancellable] = [] + + var observers: [NSKeyValueObservation] = [] + + init(child: _VariadicView.Children.Element) { + self.id = child.id + self.item = NSSplitViewItem(viewController: NSHostingController(rootView: child)) + self.collapsed = child[SplitViewItemCollapsedViewTraitKey.self] + self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] + self.item.isCollapsed = self.collapsed.wrappedValue + self.observers = createObservers() + } + + func createObservers() -> [NSKeyValueObservation] { + [ + item.observe(\.isCollapsed) { item, _ in + self.collapsed.wrappedValue = item.isCollapsed + } + ] + } + + func update(child: _VariadicView.Children.Element) { + self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] + DispatchQueue.main.async { + self.observers = [] + self.item.animator().isCollapsed = child[SplitViewItemCollapsedViewTraitKey.self].wrappedValue + self.observers = self.createObservers() + } + } +} diff --git a/CodeEdit/Features/SplitView/SplitViewModifiers.swift b/CodeEdit/Features/SplitView/SplitViewModifiers.swift new file mode 100644 index 0000000000..832a34e169 --- /dev/null +++ b/CodeEdit/Features/SplitView/SplitViewModifiers.swift @@ -0,0 +1,37 @@ +// +// SplitViewModifiers.swift +// CodeEdit +// +// Created by Wouter Hennen on 05/03/2023. +// + +import SwiftUI + +struct SplitViewControllerLayoutValueKey: _ViewTraitKey { + static var defaultValue: SplitViewController? +} + +struct SplitViewItemCollapsedViewTraitKey: _ViewTraitKey { + static var defaultValue: Binding = .constant(false) +} + +struct SplitViewItemCanCollapseViewTraitKey: _ViewTraitKey { + static var defaultValue: Bool = false +} + +extension View { + func collapsed(_ value: Binding) -> some View { + self + // Use get/set instead of binding directly, so a view update will be triggered if the binding changes. + ._trait(SplitViewItemCollapsedViewTraitKey.self, .init { + value.wrappedValue + } set: { + value.wrappedValue = $0 + }) + } + + func collapsable() -> some View { + self + ._trait(SplitViewItemCanCollapseViewTraitKey.self, true) + } +} diff --git a/CodeEdit/Features/SplitView/SplitViewReader.swift b/CodeEdit/Features/SplitView/SplitViewReader.swift new file mode 100644 index 0000000000..c4f6a4b06d --- /dev/null +++ b/CodeEdit/Features/SplitView/SplitViewReader.swift @@ -0,0 +1,51 @@ +// +// SplitViewReader.swift +// CodeEdit +// +// Created by Wouter Hennen on 05/03/2023. +// + +import SwiftUI + +struct SplitViewReader: View { + + @ViewBuilder var content: (SplitViewProxy) -> Content + + @State private var viewController: SplitViewController? + + private var proxy: SplitViewProxy { + .init { + viewController + } + } + + var body: some View { + content(proxy) + .variadic { children in + ForEach(children, id: \.id) { child in + child + .task { + if let vc = child[SplitViewControllerLayoutValueKey.self] { + viewController = vc + } + } + } + } + } +} + +struct SplitViewProxy { + private var viewController: () -> SplitViewController? + + fileprivate init(viewController: @escaping () -> SplitViewController?) { + self.viewController = viewController + } + + func setPosition(of index: Int, position: CGFloat) { + viewController()?.splitView.setPosition(position, ofDividerAt: index) + } + + func collapseView(with id: AnyHashable, _ enabled: Bool) { + viewController()?.collapse(for: id, enabled: enabled) + } +} diff --git a/CodeEdit/Features/SplitView/Variadic.swift b/CodeEdit/Features/SplitView/Variadic.swift new file mode 100644 index 0000000000..16875b61f8 --- /dev/null +++ b/CodeEdit/Features/SplitView/Variadic.swift @@ -0,0 +1,23 @@ +// +// Variadic.swift +// CodeEdit +// +// Created by Wouter Hennen on 05/03/2023. +// + +import SwiftUI + +// swiftlint:disable identifier_name +struct Helper: _VariadicView_UnaryViewRoot { + var _body: (_VariadicView.Children) -> Result + + func body(children: _VariadicView.Children) -> some View { + _body(children) + } +} + +extension View { + func variadic(@ViewBuilder process: @escaping (_VariadicView.Children) -> R) -> some View { + _VariadicView.Tree(Helper(_body: process), content: { self }) + } +} diff --git a/CodeEdit/Features/SplitView/TabManager.swift b/CodeEdit/Features/Tabs/Models/TabManager.swift similarity index 100% rename from CodeEdit/Features/SplitView/TabManager.swift rename to CodeEdit/Features/Tabs/Models/TabManager.swift diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift index acb051b621..51ea79aea2 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift @@ -23,7 +23,7 @@ class WorkspaceSplitViewData: ObservableObject { } } - // Splits the editor at a certain index into two separate editors. + /// Splits the editor at a certain index into two separate editors. func split(_ direction: Edge, at index: Int, new tabgroup: TabGroupData) { tabgroup.parent = self switch (axis, direction) { diff --git a/CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift similarity index 100% rename from CodeEdit/Features/SplitView/WorkspaceTabGroupView.swift rename to CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index 46324542dc..ed8b931706 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -95,33 +95,31 @@ struct TabBarContextMenu: ViewModifier { Divider() - if let item = item as? WorkspaceClient.FileItem { - Group { - Button("Copy Path") { - copyPath(item: item) - } + Group { + Button("Copy Path") { + copyPath(item: item) + } - Button("Copy Relative Path") { - copyRelativePath(item: item) - } + Button("Copy Relative Path") { + copyRelativePath(item: item) } + } - Divider() + Divider() - Group { - Button("Show in Finder") { - item.showInFinder() - } + Group { + Button("Show in Finder") { + item.showInFinder() + } - Button("Reveal in Project Navigator") { - workspace.listenerModel.highlightedFileItem = item - } + Button("Reveal in Project Navigator") { + workspace.listenerModel.highlightedFileItem = item + } - Button("Open in New Window") { + Button("Open in New Window") { - } - .disabled(true) } + .disabled(true) } }) } diff --git a/CodeEdit/Utils/WorkspaceClient/Model/FileItem.swift b/CodeEdit/Utils/WorkspaceClient/Model/FileItem.swift index 4092ae320a..7ad7f7e742 100644 --- a/CodeEdit/Utils/WorkspaceClient/Model/FileItem.swift +++ b/CodeEdit/Utils/WorkspaceClient/Model/FileItem.swift @@ -216,6 +216,7 @@ extension WorkspaceClient { /// This function allows creating files in the selected folder or project main directory /// - Parameter fileName: The name of the new file + @discardableResult func addFile(fileName: String) -> String { // check if folder, if it is create file under self var fileUrl = (self.isFolder ? From a5c6d6aab3c9cbfb7be46de7a3770c224be682ad Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 19:24:01 +0100 Subject: [PATCH 50/82] Fixed scrolling to active tab Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarView.swift | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 68826de32e..b1a4347589 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -35,7 +35,7 @@ struct TabBarView: View { @EnvironmentObject private var tabManager: TabManager @EnvironmentObject - private var tabs: TabGroupData + private var tabgroup: TabGroupData @Environment(\.splitEditor) var splitEditor @@ -135,7 +135,7 @@ struct TabBarView: View { private func updateExpectedTabWidth(proxy: GeometryProxy) { expectedTabWidth = max( // Equally divided size of a native tab. - (proxy.size.width + 1) / CGFloat(tabs.tabs.count) + 1, + (proxy.size.width + 1) / CGFloat(tabgroup.tabs.count) + 1, // Min size of a native tab. CGFloat(140) ) @@ -249,8 +249,8 @@ struct TabBarView: View { // In order to avoid the lag due to the update of workspace state. DispatchQueue.main.asyncAfter(deadline: .now() + 0.40) { if draggingStartLocation == nil { - tabs.tabs = .init(openedTabs.compactMap { id in - tabs.tabs.first { $0.id == id } + tabgroup.tabs = .init(openedTabs.compactMap { id in + tabgroup.tabs.first { $0.id == id } }) // workspace.reorderedTabs(openedTabs: openedTabs) // TODO: Fix save state @@ -288,7 +288,7 @@ struct TabBarView: View { /// Called when the tab count changes or the temporary tab changes. /// - Parameter geometryProxy: The geometry proxy to calculate the new width using. private func updateForTabCountChange(geometryProxy: GeometryProxy) { - openedTabs = tabs.tabs.map(\.id) + openedTabs = tabgroup.tabs.map(\.id) // Only update the expected width when user is not hovering over tabs. // This should give users a better experience on closing multiple tabs continuously. @@ -305,14 +305,14 @@ struct TabBarView: View { leadingAccessories // Tab bar items. GeometryReader { geometryProxy in - ScrollViewReader { scrollReader in - ScrollView(.horizontal, showsIndicators: false) { + ScrollView(.horizontal, showsIndicators: false) { + ScrollViewReader { scrollReader in HStack( alignment: .center, spacing: -1 // Negative spacing for overlapping the divider. ) { ForEach(Array(openedTabs.enumerated()), id: \.element) { index, id in - if let item = tabs.tabs.first(where: { $0.id == id }) { + if let item = tabgroup.tabs.first(where: { $0.id == id }) { TabBarItemView( expectedWidth: expectedTabWidth, item: item, @@ -353,33 +353,32 @@ struct TabBarView: View { // This padding is to hide dividers at two ends under the accessory view divider. .padding(.horizontal, prefs.preferences.general.tabBarStyle == .native ? -1 : 0) .onAppear { - openedTabs = tabs.tabs.map(\.id) + openedTabs = tabgroup.tabs.map(\.id) // On view appeared, compute the initial expected width for tabs. updateExpectedTabWidth(proxy: geometryProxy) // On first tab appeared, jump to the corresponding position. - scrollReader.scrollTo(tabs.selected) + scrollReader.scrollTo(tabgroup.selected) } - .onChange(of: tabs.tabs.count) { _ in + .onChange(of: tabgroup.tabs.count) { _ in withAnimation( .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) ) { updateForTabCountChange(geometryProxy: geometryProxy) } } - .onChange(of: tabs.tabs) { _ in + .onChange(of: tabgroup.tabs) { _ in updateForTabCountChange(geometryProxy: geometryProxy) DispatchQueue.main.asyncAfter( deadline: .now() + .milliseconds( prefs.preferences.general.tabBarStyle == .native ? 150 : 200 ) ) { - scrollReader.scrollTo(tabs.selected) + scrollReader.scrollTo(tabgroup.selected?.id) } } // When selected tab is changed, scroll to it if possible. - .onChange(of: tabs.selected) { targetId in - guard let selectedId = targetId else { return } - scrollReader.scrollTo(selectedId) + .onChange(of: tabgroup.selected) { + scrollReader.scrollTo($0?.id) } // When window size changes, re-compute the expected tab width. @@ -433,12 +432,12 @@ struct TabBarView: View { private var leadingAccessories: some View { HStack(spacing: 2) { - if tabManager.tabGroups.findSomeTabGroup(except: tabs) != nil { + if tabManager.tabGroups.findSomeTabGroup(except: tabgroup) != nil { TabBarAccessoryIcon( icon: .init(systemName: "multiply"), action: { - tabs.close() - if tabManager.activeTabGroup == tabs { + tabgroup.close() + if tabManager.activeTabGroup == tabgroup { tabManager.activeTabGroup = tabManager.activeTabGroupHistory.removeFirst() } } @@ -453,11 +452,11 @@ struct TabBarView: View { Group { Menu { ForEach( - Array(tabs.history.dropFirst(tabs.historyOffset+1).enumerated()), - id: \.element + Array(tabgroup.history.dropFirst(tabgroup.historyOffset+1).enumerated()), + id: \.offset ) { index, tab in Button { - tabs.historyOffset += index + 1 + tabgroup.historyOffset += index + 1 } label: { HStack { tab.icon @@ -468,20 +467,20 @@ struct TabBarView: View { } label: { Image(systemName: "chevron.left") .controlSize(.regular) - .brightness(tabs.historyOffset == tabs.history.count-1 || tabs.history.isEmpty ? -0.4 : 0.0) + .brightness(tabgroup.historyOffset == tabgroup.history.count-1 || tabgroup.history.isEmpty ? -0.4 : 0.0) } primaryAction: { - tabs.historyOffset += 1 + tabgroup.historyOffset += 1 } - .disabled(tabs.historyOffset == tabs.history.count-1 || tabs.history.isEmpty) + .disabled(tabgroup.historyOffset == tabgroup.history.count-1 || tabgroup.history.isEmpty) .help("Navigate back") Menu { ForEach( - Array(tabs.history.prefix(tabs.historyOffset).reversed().enumerated()), - id: \.element + Array(tabgroup.history.prefix(tabgroup.historyOffset).reversed().enumerated()), + id: \.offset ) { index, tab in Button { - tabs.historyOffset -= index + 1 + tabgroup.historyOffset -= index + 1 } label: { HStack { tab.icon @@ -492,11 +491,11 @@ struct TabBarView: View { } label: { Image(systemName: "chevron.right") .controlSize(.regular) - .brightness(tabs.historyOffset == 0 ? -0.4 : 0.0) + .brightness(tabgroup.historyOffset == 0 ? -0.4 : 0.0) } primaryAction: { - tabs.historyOffset -= 1 + tabgroup.historyOffset -= 1 } - .disabled(tabs.historyOffset == 0) + .disabled(tabgroup.historyOffset == 0) .help("Navigate forward") } .controlSize(.small) @@ -552,7 +551,7 @@ struct TabBarView: View { } var splitViewButtonAxis: Axis { - switch (tabs.parent!.axis, modifierKeys.contains(.option)) { + switch (tabgroup.parent!.axis, modifierKeys.contains(.option)) { case (.horizontal, true), (.vertical, false): return .vertical @@ -573,7 +572,7 @@ struct TabBarView: View { } func split(edge: Edge) { - if let tab = tabs.selected { + if let tab = tabgroup.selected { splitEditor(edge, .init(files: [tab])) } else { splitEditor(edge, .init()) From 1c08816584d1f1e6a6d4f27df615294a0379caad Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Sun, 5 Mar 2023 19:25:11 +0100 Subject: [PATCH 51/82] Fixed warning Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarView.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index b1a4347589..09594bf39a 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -467,7 +467,10 @@ struct TabBarView: View { } label: { Image(systemName: "chevron.left") .controlSize(.regular) - .brightness(tabgroup.historyOffset == tabgroup.history.count-1 || tabgroup.history.isEmpty ? -0.4 : 0.0) + .brightness( + tabgroup.historyOffset == tabgroup.history.count-1 || tabgroup.history.isEmpty + ? -0.4 : 0.0 + ) } primaryAction: { tabgroup.historyOffset += 1 } From 584b13c74ca8632c604a541591d9fa64747a4ff5 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 00:35:49 +0100 Subject: [PATCH 52/82] Fixed tabbar scroll glitch Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarView.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 09594bf39a..ead85d43a6 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -359,13 +359,6 @@ struct TabBarView: View { // On first tab appeared, jump to the corresponding position. scrollReader.scrollTo(tabgroup.selected) } - .onChange(of: tabgroup.tabs.count) { _ in - withAnimation( - .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) - ) { - updateForTabCountChange(geometryProxy: geometryProxy) - } - } .onChange(of: tabgroup.tabs) { _ in updateForTabCountChange(geometryProxy: geometryProxy) DispatchQueue.main.asyncAfter( From 2bc1cf2028fffe6d127fbe856a42002f17cc1ff6 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 01:56:49 +0100 Subject: [PATCH 53/82] fixed tabbarview active state Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 4 +++ .../TabGroup/Environment+ActiveTabGroup.swift | 19 +++++++++++++ .../Tabs/TabGroup/WorkspaceTabGroupView.swift | 2 +- .../Tabs/Views/TabBarItemBackground.swift | 5 +++- .../Features/Tabs/Views/TabBarItemView.swift | 27 ++++++++++--------- 5 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 CodeEdit/Features/Tabs/TabGroup/Environment+ActiveTabGroup.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index fc1b5834db..1fac8231fd 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -334,6 +334,7 @@ 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */; }; 6C97EBCF297876E500302F95 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCE297876E500302F95 /* AboutWindowController.swift */; }; 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */; }; + 6CC9E4B229B5669900C97388 /* Environment+ActiveTabGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC9E4B129B5669900C97388 /* Environment+ActiveTabGroup.swift */; }; 6CDA84AD284C1BA000C1CC3A /* TabBarContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDA84AC284C1BA000C1CC3A /* TabBarContextMenu.swift */; }; B60BE8BD297A167600841125 /* AcknowledgementRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */; }; B62617282964924E00E866AB /* CodeEditKit in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 2801BB89290D5A8E00EBF552 /* CodeEditKit */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -731,6 +732,7 @@ 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; 6C97EBCE297876E500302F95 /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; 6CBD1BC52978DE53006639D5 /* Font+Caption3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Caption3.swift"; sourceTree = ""; }; + 6CC9E4B129B5669900C97388 /* Environment+ActiveTabGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ActiveTabGroup.swift"; sourceTree = ""; }; 6CDA84AC284C1BA000C1CC3A /* TabBarContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarContextMenu.swift; sourceTree = ""; }; B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementRowView.swift; sourceTree = ""; }; B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodeEdit.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2200,6 +2202,7 @@ isa = PBXGroup; children = ( 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */, + 6CC9E4B129B5669900C97388 /* Environment+ActiveTabGroup.swift */, 6C147C3F29A328560089B630 /* WorkspaceSplitViewData.swift */, 6C147C3E29A3281D0089B630 /* TabGroup.swift */, 6C147C3D29A3281D0089B630 /* TabGroupData.swift */, @@ -2758,6 +2761,7 @@ 58F2EB04292FB2B0004A9BDE /* SourceControlPreferences.swift in Sources */, 582213F0291834A500EFE361 /* AboutView.swift in Sources */, 58F2EB12292FB2B0004A9BDE /* PreferencesPlaceholderView.swift in Sources */, + 6CC9E4B229B5669900C97388 /* Environment+ActiveTabGroup.swift in Sources */, 58822526292C280D00E83CDE /* StatusBarBreakpointButton.swift in Sources */, 58D01C96293167DC00C5B6B4 /* Date+Formatted.swift in Sources */, 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */, diff --git a/CodeEdit/Features/Tabs/TabGroup/Environment+ActiveTabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/Environment+ActiveTabGroup.swift new file mode 100644 index 0000000000..fa63f2e38f --- /dev/null +++ b/CodeEdit/Features/Tabs/TabGroup/Environment+ActiveTabGroup.swift @@ -0,0 +1,19 @@ +// +// Environment+ActiveTabGroup.swift +// CodeEdit +// +// Created by Wouter Hennen on 06/03/2023. +// + +import SwiftUI + +struct ActiveTabGroupEnvironmentKey: EnvironmentKey { + static var defaultValue = false +} + +extension EnvironmentValues { + var isActiveTabGroup: Bool { + get { self[ActiveTabGroupEnvironmentKey.self] } + set { self[ActiveTabGroupEnvironmentKey.self] = newValue } + } +} diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift index ffdc4ed7a7..82861203e3 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift @@ -52,7 +52,7 @@ struct WorkspaceTabGroupView: View { Divider() } } - .environment(\.controlActiveState, tabgroup == tabManager.activeTabGroup ? .key : .inactive) + .environment(\.isActiveTabGroup, tabgroup == tabManager.activeTabGroup) .background(EffectView(.titlebar, blendingMode: .withinWindow, emphasized: false)) } .focused($isFocused) diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift b/CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift index 4616914e89..b6292206c0 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift @@ -18,6 +18,9 @@ struct TabBarItemBackground: View { @Environment(\.controlActiveState) private var activeState + @Environment(\.isActiveTabGroup) + private var isActiveTabGroup + private var inHoldingState: Bool { isPressing || isDragging } @@ -32,7 +35,7 @@ struct TabBarItemBackground: View { Color(.controlAccentColor) .hueRotation(.degrees(-5)) .opacity( - isActive + isActive && isActiveTabGroup ? colorScheme == .dark ? activeState == .inactive ? 0.22 : inHoldingState ? 0.33 : 0.26 : activeState == .inactive ? 0.1 : inHoldingState ? 0.27 : 0.2 diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index 1ab831ee18..e14d1a9001 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -17,6 +17,9 @@ struct TabBarItemView: View { @Environment(\.controlActiveState) private var activeState + @Environment(\.isActiveTabGroup) + private var isActiveTabGroup + @Environment(\.isFullscreen) private var isFullscreen @@ -62,7 +65,7 @@ struct TabBarItemView: View { private var closeButtonGestureActive: Bool @EnvironmentObject - private var tabs: TabGroupData + private var tabgroup: TabGroupData /// The item associated with the current tab. /// @@ -72,12 +75,12 @@ struct TabBarItemView: View { var index: Int private var isTemporary: Bool { - isActive + isActive && tabgroup.selectedIsTemporary } /// Is the current tab the active tab. private var isActive: Bool { - item == tabs.selected + item == tabgroup.selected } /// Is the current tab being dragged. @@ -95,12 +98,12 @@ struct TabBarItemView: View { /// Switch the active tab to current tab. private func switchAction() { // Only set the `selectedId` when they are not equal to avoid performance issue for now. - tabManager.activeTabGroup = tabs - if tabs.selected != item { - tabs.selected = item - tabs.history.removeFirst(tabs.historyOffset) - tabs.history.prepend(item) - tabs.historyOffset = 0 + tabManager.activeTabGroup = tabgroup + if tabgroup.selected != item { + tabgroup.selected = item + tabgroup.history.removeFirst(tabgroup.historyOffset) + tabgroup.history.prepend(item) + tabgroup.historyOffset = 0 } } @@ -110,7 +113,7 @@ struct TabBarItemView: View { withAnimation( .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) ) { - tabs.closeTab(item: item) + tabgroup.closeTab(item: item) } } @@ -145,7 +148,7 @@ struct TabBarItemView: View { .resizable() .aspectRatio(contentMode: .fit) .foregroundColor( - prefs.preferences.general.fileIconStyle == .color && activeState != .inactive + prefs.preferences.general.fileIconStyle == .color && activeState != .inactive && isActiveTabGroup ? item.iconColor : .secondary ) @@ -295,7 +298,7 @@ struct TabBarItemView: View { TapGesture(count: 2) .onEnded { _ in if isTemporary { - tabs.selectedIsTemporary = false + tabgroup.selectedIsTemporary = false } } ) From 0495ff82284d8dad98c5aa62ea547571130d93f7 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 02:02:01 +0100 Subject: [PATCH 54/82] renamed WorkspaceSplitViewData to SplitViewData Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 8 ++++---- CodeEdit/Features/SplitView/EditorView.swift | 2 +- .../SplitViewData.swift} | 2 +- CodeEdit/Features/Tabs/TabGroup/TabGroup.swift | 4 ++-- CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) rename CodeEdit/Features/{Tabs/TabGroup/WorkspaceSplitViewData.swift => SplitView/SplitViewData.swift} (97%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 1fac8231fd..50fa5692dc 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -304,7 +304,7 @@ 58FD7609291EA1CB0051D6E4 /* CommandPaletteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD7607291EA1CB0051D6E4 /* CommandPaletteView.swift */; }; 5C4BB1E128212B1E00A92FB2 /* World.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4BB1E028212B1E00A92FB2 /* World.swift */; }; 6C05A8AF284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */; }; - 6C147C4029A328BC0089B630 /* WorkspaceSplitViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3F29A328560089B630 /* WorkspaceSplitViewData.swift */; }; + 6C147C4029A328BC0089B630 /* SplitViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3F29A328560089B630 /* SplitViewData.swift */; }; 6C147C4129A328BF0089B630 /* TabGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3E29A3281D0089B630 /* TabGroup.swift */; }; 6C147C4229A328C10089B630 /* TabGroupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C147C3D29A3281D0089B630 /* TabGroupData.swift */; }; 6C147C4529A329350089B630 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 6C147C4429A329350089B630 /* OrderedCollections */; }; @@ -707,7 +707,7 @@ 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+Listeners.swift"; sourceTree = ""; }; 6C147C3D29A3281D0089B630 /* TabGroupData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabGroupData.swift; sourceTree = ""; }; 6C147C3E29A3281D0089B630 /* TabGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabGroup.swift; sourceTree = ""; }; - 6C147C3F29A328560089B630 /* WorkspaceSplitViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSplitViewData.swift; sourceTree = ""; }; + 6C147C3F29A328560089B630 /* SplitViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewData.swift; sourceTree = ""; }; 6C147C4829A32A080089B630 /* EditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorView.swift; sourceTree = ""; }; 6C147C4A29A32A7B0089B630 /* Environment+SplitEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+SplitEditor.swift"; sourceTree = ""; }; 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceTabGroupView.swift; sourceTree = ""; }; @@ -2203,7 +2203,6 @@ children = ( 6C147C4C29A32AA30089B630 /* WorkspaceTabGroupView.swift */, 6CC9E4B129B5669900C97388 /* Environment+ActiveTabGroup.swift */, - 6C147C3F29A328560089B630 /* WorkspaceSplitViewData.swift */, 6C147C3E29A3281D0089B630 /* TabGroup.swift */, 6C147C3D29A3281D0089B630 /* TabGroupData.swift */, ); @@ -2222,6 +2221,7 @@ 6C2C155929B4F4CC00EA60A5 /* Variadic.swift */, 6C7256D629A3D7D000C2D3E0 /* SplitViewControllerView.swift */, 6C2C155729B4F49100EA60A5 /* SplitViewItem.swift */, + 6C147C3F29A328560089B630 /* SplitViewData.swift */, ); path = SplitView; sourceTree = ""; @@ -2886,7 +2886,7 @@ 28B0A19827E385C300B73177 /* NavigatorSidebarToolbarTop.swift in Sources */, 587B9E8629301D8F00AC7927 /* GitHubComment.swift in Sources */, 58F2EAE9292FB2B0004A9BDE /* SourceControlPreferencesView.swift in Sources */, - 6C147C4029A328BC0089B630 /* WorkspaceSplitViewData.swift in Sources */, + 6C147C4029A328BC0089B630 /* SplitViewData.swift in Sources */, 587B9E9029301D8F00AC7927 /* BitBucketTokenRouter.swift in Sources */, B6C6A42E29771A8D00A3D28F /* TabBarItemButtonStyle.swift in Sources */, 58822525292C280D00E83CDE /* StatusBarMenuLabel.swift in Sources */, diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index 135545963b..fe7d7d316f 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -41,7 +41,7 @@ struct EditorView: View { } struct SubEditorView: View { - @ObservedObject var data: WorkspaceSplitViewData + @ObservedObject var data: SplitViewData var body: some View { SplitView(axis: data.axis) { diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift b/CodeEdit/Features/SplitView/SplitViewData.swift similarity index 97% rename from CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift rename to CodeEdit/Features/SplitView/SplitViewData.swift index 51ea79aea2..48a68ce98e 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceSplitViewData.swift +++ b/CodeEdit/Features/SplitView/SplitViewData.swift @@ -7,7 +7,7 @@ import SwiftUI -class WorkspaceSplitViewData: ObservableObject { +class SplitViewData: ObservableObject { @Published var tabgroups: [TabGroup] var axis: Axis diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift index cdbb2b34d2..0c7a4294f1 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift @@ -9,8 +9,8 @@ import Foundation enum TabGroup { case one(TabGroupData) - case vertical(WorkspaceSplitViewData) - case horizontal(WorkspaceSplitViewData) + case vertical(SplitViewData) + case horizontal(SplitViewData) func closeAllTabs(of file: WorkspaceClient.FileItem) { switch self { diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 285f6a70d1..733b8f8b18 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -59,12 +59,12 @@ final class TabGroupData: ObservableObject, Identifiable { let id = UUID() - weak var parent: WorkspaceSplitViewData? + weak var parent: SplitViewData? init( files: OrderedSet = [], selected: Tab? = nil, - parent: WorkspaceSplitViewData? = nil + parent: SplitViewData? = nil ) { self.tabs = [] self.parent = parent From ddf9e63f7367b68cfb21ef190224faa4c3f0057d Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 02:44:03 +0100 Subject: [PATCH 55/82] adds splitview flattening Signed-off-by: Wouter01 --- .../Features/SplitView/SplitViewData.swift | 7 +++++++ CodeEdit/Features/Tabs/Models/TabManager.swift | 7 +++++++ CodeEdit/Features/Tabs/TabGroup/TabGroup.swift | 18 ++++++++++++++++++ CodeEdit/Features/Tabs/Views/TabBarView.swift | 1 + 4 files changed, 33 insertions(+) diff --git a/CodeEdit/Features/SplitView/SplitViewData.swift b/CodeEdit/Features/SplitView/SplitViewData.swift index 48a68ce98e..45f6c444e6 100644 --- a/CodeEdit/Features/SplitView/SplitViewData.swift +++ b/CodeEdit/Features/SplitView/SplitViewData.swift @@ -57,4 +57,11 @@ class SplitViewData: ObservableObject { return false } } + + /// Flattens the splitviews. + func flatten() { + for index in tabgroups.indices { + tabgroups[index].flatten(parent: self) + } + } } diff --git a/CodeEdit/Features/Tabs/Models/TabManager.swift b/CodeEdit/Features/Tabs/Models/TabManager.swift index 4892804947..3887bebf68 100644 --- a/CodeEdit/Features/Tabs/Models/TabManager.swift +++ b/CodeEdit/Features/Tabs/Models/TabManager.swift @@ -31,6 +31,13 @@ class TabManager: ObservableObject { self.tabGroups = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) } + /// Flattens the splitviews. + func flatten() { + if case .horizontal(let data) = tabGroups { + data.flatten() + } + } + /// Opens a new tab in a tabgroup. If no tabgroup is given, it is added to the active tab group. func openTab(item: WorkspaceClient.FileItem, in tabgroup: TabGroupData? = nil) { let tabgroup = tabgroup ?? activeTabGroup diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift index 0c7a4294f1..60d8676d7f 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift @@ -49,4 +49,22 @@ enum TabGroup { return data.tabgroups.map { $0.gatherOpenFiles() }.reduce(into: []) { $0.formUnion($1) } } } + + /// Flattens the splitviews. + mutating func flatten(parent: SplitViewData) { + switch self { + case .one: + break + case .horizontal(let data), .vertical(let data): + if data.tabgroups.count == 1 { + let one = data.tabgroups[0] + if case .one(let tabGroupData) = one { + tabGroupData.parent = parent + } + self = one + } else { + data.flatten() + } + } + } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index ead85d43a6..92f7171835 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -433,6 +433,7 @@ struct TabBarView: View { if tabManager.activeTabGroup == tabgroup { tabManager.activeTabGroup = tabManager.activeTabGroupHistory.removeFirst() } + tabManager.flatten() } ) .help("Close Tab Group") From b8c23f389992875a9de343ad2da4e0e29a482d1b Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 02:46:00 +0100 Subject: [PATCH 56/82] Fixed warning Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarItemView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index e14d1a9001..330d40440d 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -148,7 +148,8 @@ struct TabBarItemView: View { .resizable() .aspectRatio(contentMode: .fit) .foregroundColor( - prefs.preferences.general.fileIconStyle == .color && activeState != .inactive && isActiveTabGroup + prefs.preferences.general.fileIconStyle == .color + && activeState != .inactive && isActiveTabGroup ? item.iconColor : .secondary ) From c5471fee852027c317fb949cbf1517a54d440f46 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 02:48:39 +0100 Subject: [PATCH 57/82] Removed unused dependency Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 17 ----------------- .../xcshareddata/swiftpm/Package.resolved | 9 --------- 2 files changed, 26 deletions(-) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 50fa5692dc..cee8fe7162 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -327,7 +327,6 @@ 6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */; }; 6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; }; 6C7256D729A3D7D000C2D3E0 /* SplitViewControllerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* SplitViewControllerView.swift */; }; - 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */; }; 6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */; }; 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 6C81916A29B41DD300B75C92 /* DequeModule */; }; 6C91D57229B176FF0059A90D /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C91D57129B176FF0059A90D /* TabManager.swift */; }; @@ -784,7 +783,6 @@ 5879826B292EC7B00085B254 /* Light-Swift-Untar in Frameworks */, 5879828A292ED15F0085B254 /* SwiftTerm in Frameworks */, 2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */, - 6C7256DA29A3D98C00C2D3E0 /* SequenceBuilder in Frameworks */, 6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2391,7 +2389,6 @@ 58F2EB19292FB91C004A9BDE /* Preferences */, 58F2EB1D292FB954004A9BDE /* Sparkle */, 6C147C4429A329350089B630 /* OrderedCollections */, - 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */, 6C81916A29B41DD300B75C92 /* DequeModule */, ); productName = CodeEdit; @@ -2487,7 +2484,6 @@ 58F2EB1C292FB954004A9BDE /* XCRemoteSwiftPackageReference "Sparkle" */, 583E529A29361BAB001AB554 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */, - 6C7256D829A3D98C00C2D3E0 /* XCRemoteSwiftPackageReference "SequenceBuilder" */, ); productRefGroup = B658FB2D27DA9E0F00EA4DBD /* Products */; projectDirPath = ""; @@ -3870,14 +3866,6 @@ minimumVersion = 1.0.0; }; }; - 6C7256D829A3D98C00C2D3E0 /* XCRemoteSwiftPackageReference "SequenceBuilder" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/andtie/SequenceBuilder"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.0.7; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3936,11 +3924,6 @@ package = 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = OrderedCollections; }; - 6C7256D929A3D98C00C2D3E0 /* SequenceBuilder */ = { - isa = XCSwiftPackageProductDependency; - package = 6C7256D829A3D98C00C2D3E0 /* XCRemoteSwiftPackageReference "SequenceBuilder" */; - productName = SequenceBuilder; - }; 6C81916A29B41DD300B75C92 /* DequeModule */ = { isa = XCSwiftPackageProductDependency; package = 6C147C4329A329350089B630 /* XCRemoteSwiftPackageReference "swift-collections" */; diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8a58ec3753..2f6d76eadb 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -72,15 +72,6 @@ "version" : "1.5.3" } }, - { - "identity" : "sequencebuilder", - "kind" : "remoteSourceControl", - "location" : "https://github.com/andtie/SequenceBuilder", - "state" : { - "revision" : "54d3d1eff31a7e35122f616840fff11899ea85b4", - "version" : "0.0.7" - } - }, { "identity" : "sparkle", "kind" : "remoteSourceControl", From 58f77127eacbe12b9c553ca0300a6c21c9b2e937 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 03:27:40 +0100 Subject: [PATCH 58/82] Changed representation of temporary tab Signed-off-by: Wouter01 --- .../CodeEditWindowController.swift | 2 +- .../Features/Tabs/TabGroup/TabGroupData.swift | 37 ++++++++++++------- .../Tabs/TabGroup/WorkspaceTabGroupView.swift | 2 +- .../Tabs/Views/TabBarContextMenu.swift | 2 +- .../Features/Tabs/Views/TabBarItemView.swift | 4 +- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index ad58b4a612..604779ce2e 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -247,7 +247,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { @IBAction func saveDocument(_ sender: Any) { getSelectedCodeFile()?.save(sender) - workspace?.tabManager.activeTabGroup.selectedIsTemporary = false + workspace?.tabManager.activeTabGroup.temporaryTab = nil } @IBAction func openCommandPalette(_ sender: Any) { diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 733b8f8b18..2b41190d5f 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -55,7 +55,7 @@ final class TabGroupData: ObservableObject, Identifiable { /// Currently selected tab. @Published var selected: Tab? - var selectedIsTemporary = false + @Published var temporaryTab: Tab? let id = UUID() @@ -118,7 +118,7 @@ final class TabGroupData: ObservableObject, Identifiable { /// Opens a tab in the tabgroup. /// If a tab for the item already exists, it is used instead. - func openTab(item: Tab, asTemporary: Bool, fromHistory: Bool = false) { + func openTab(item: Tab, asTemporary: Bool) { // Item is already opened in a tab. guard !tabs.contains(item) || !asTemporary else { selected = item @@ -126,21 +126,30 @@ final class TabGroupData: ObservableObject, Identifiable { return } - if let selected, let index = tabs.firstIndex(of: selected), asTemporary && selectedIsTemporary { - // Replace temporary tab - history.prepend(item) - tabs.remove(selected) - tabs.insert(item, at: index) - self.selected = item - } else if selectedIsTemporary && !asTemporary { - // Temporary becomes permanent. - selectedIsTemporary = false + switch (temporaryTab, asTemporary) { + case (.some(let tab), true): + if let index = tabs.firstIndex(of: tab) { + history.prepend(item) + tabs.remove(tab) + tabs.insert(item, at: index) + self.selected = item + temporaryTab = item + } - } else { - // New temporary tab + case (.some(let tab), false) where tab == item: + temporaryTab = nil + + case (.none, true): openTab(item: item) - selectedIsTemporary = true + temporaryTab = item + + case (.none, false): + openTab(item: item) + + default: + break } + do { try openFile(item: item) } catch { diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift index 82861203e3..0a657a83a7 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift @@ -63,7 +63,7 @@ struct WorkspaceTabGroupView: View { } .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CodeEditor.didBeginEditing"))) { _ in print("Not temporary anymore") - tabgroup.selectedIsTemporary = false + tabgroup.temporaryTab = nil } } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index ed8b931706..cfe43c6321 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -88,7 +88,7 @@ struct TabBarContextMenu: ViewModifier { if isTemporary { Button("Keep Open") { - tabs.selectedIsTemporary = false + tabs.temporaryTab = nil } } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index 330d40440d..54052ac653 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -75,7 +75,7 @@ struct TabBarItemView: View { var index: Int private var isTemporary: Bool { - isActive && tabgroup.selectedIsTemporary + tabgroup.temporaryTab == item } /// Is the current tab the active tab. @@ -299,7 +299,7 @@ struct TabBarItemView: View { TapGesture(count: 2) .onEnded { _ in if isTemporary { - tabgroup.selectedIsTemporary = false + tabgroup.temporaryTab = nil } } ) From 2b6674edbf6a4b334e1d533e61870f845e67e2d5 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 03:46:57 +0100 Subject: [PATCH 59/82] Fixed some issues regarding tabbarview Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarView.swift | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 92f7171835..8e6fdc5552 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -359,19 +359,28 @@ struct TabBarView: View { // On first tab appeared, jump to the corresponding position. scrollReader.scrollTo(tabgroup.selected) } - .onChange(of: tabgroup.tabs) { _ in - updateForTabCountChange(geometryProxy: geometryProxy) - DispatchQueue.main.asyncAfter( - deadline: .now() + .milliseconds( - prefs.preferences.general.tabBarStyle == .native ? 150 : 200 - ) - ) { - scrollReader.scrollTo(tabgroup.selected?.id) + .onChange(of: tabgroup.tabs) { [tabs = tabgroup.tabs] newValue in + if tabs.count == newValue.count { + updateForTabCountChange(geometryProxy: geometryProxy) + } else { + withAnimation( + .easeOut(duration: prefs.preferences.general.tabBarStyle == .native ? 0.15 : 0.20) + ) { + updateForTabCountChange(geometryProxy: geometryProxy) + } + } + Task { + try? await Task.sleep(for: .milliseconds(300)) + withAnimation { + scrollReader.scrollTo(tabgroup.selected?.id) + } } } // When selected tab is changed, scroll to it if possible. - .onChange(of: tabgroup.selected) { - scrollReader.scrollTo($0?.id) + .onChange(of: tabgroup.selected) { newValue in + withAnimation { + scrollReader.scrollTo(newValue?.id) + } } // When window size changes, re-compute the expected tab width. From 53a1f5a369924d91534b4ebb951487772a0cf3ba Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 03:53:43 +0100 Subject: [PATCH 60/82] Fixed animation Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 8e6fdc5552..1c5570b597 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -324,7 +324,7 @@ struct TabBarView: View { .transition( .asymmetric( insertion: .offset(x: -14).combined(with: .opacity), - removal: .scale(scale: 0.7).combined(with: .opacity) + removal: .opacity ) ) .frame(height: TabBarView.height) From c1a3f87740e349c7388773c77660b041d43fb5c0 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 04:00:03 +0100 Subject: [PATCH 61/82] Removed splitview button animation Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarView.swift | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 1c5570b597..bd3e336028 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -522,12 +522,6 @@ struct TabBarView: View { } } -// var splitViewAxisButton: Axis { -// switch (tabs.parent?.axis, modifierKeys) { -// case (.horizontal, .option) -// } -// } - private var trailingAccessories: some View { HStack(spacing: 2) { TabBarAccessoryIcon( @@ -556,25 +550,24 @@ struct TabBarView: View { } } - var splitViewButtonAxis: Axis { - switch (tabgroup.parent!.axis, modifierKeys.contains(.option)) { - case (.horizontal, true), (.vertical, false): - return .vertical - - case (.vertical, true), (.horizontal, false): - return .horizontal - } - } - var splitviewButton: some View { - TabBarAccessoryIcon(icon: Image(systemName: "square.split.2x1")) { - split(edge: splitViewButtonAxis == .vertical ? .bottom : .trailing) + Group { + switch (tabgroup.parent!.axis, modifierKeys.contains(.option)) { + case (.horizontal, true), (.vertical, false): + TabBarAccessoryIcon(icon: Image(systemName: "square.split.1x2")) { + split(edge: .bottom) + } + .help("Split Vertically") + + case (.vertical, true), (.horizontal, false): + TabBarAccessoryIcon(icon: Image(systemName: "square.split.2x1")) { + split(edge: .trailing) + } + .help("Split Horizontally") + } } - .rotationEffect(splitViewButtonAxis == .vertical ? .degrees(90) : .zero) - .animation(.interactiveSpring(), value: splitViewButtonAxis) .foregroundColor(.secondary) .buttonStyle(.plain) - .help("Split \(splitViewButtonAxis == .vertical ? "Vertically" : "Horizontally")") } func split(edge: Edge) { From 6ae26e835d339f7b64bf89706785901e889e7b37 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 12:25:10 +0100 Subject: [PATCH 62/82] fixed text coloring in light mode Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarItemView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index 54052ac653..e9eb3868ef 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -234,7 +234,7 @@ struct TabBarItemView: View { .opacity(prefs.preferences.general.tabBarStyle == .native && !isActive ? 1 : 0) } .foregroundColor( - isActive + isActive && isActiveTabGroup ? ( prefs.preferences.general.tabBarStyle == .xcode && colorScheme != .dark ? Color(nsColor: .controlAccentColor) From 7915d6555dbf7881e2ce9348d91fe488e798030d Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 12:41:25 +0100 Subject: [PATCH 63/82] improvements to tabbar and breadcrumbs in light mode Signed-off-by: Wouter01 --- CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift | 8 ++++++-- CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift | 3 ++- CodeEdit/Features/Tabs/Views/TabBarView.swift | 6 +++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift index 48fd9fd901..e157910f4b 100644 --- a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift +++ b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift @@ -15,6 +15,9 @@ struct BreadcrumbsView: View { @Environment(\.colorScheme) private var colorScheme + @Environment(\.isActiveTabGroup) + private var isActiveTabGroup + @Environment(\.controlActiveState) private var activeState @@ -54,8 +57,9 @@ struct BreadcrumbsView: View { .padding(.horizontal, 10) } .frame(height: Self.height, alignment: .center) - .saturation(activeState == .inactive ? 0.0 : 1.0) - .brightness(activeState == .inactive ? -0.1 : 0.0) + .opacity(activeState == .inactive ? 0.8 : 1.0) + .saturation(isActiveTabGroup ? 1.0 : 0.0) + .brightness(isActiveTabGroup ? 0.0 : -0.1) .background(EffectView(.headerView).frame(height: Self.height)) } diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift b/CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift index b6292206c0..143afde2e5 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemBackground.swift @@ -35,12 +35,13 @@ struct TabBarItemBackground: View { Color(.controlAccentColor) .hueRotation(.degrees(-5)) .opacity( - isActive && isActiveTabGroup + isActive ? colorScheme == .dark ? activeState == .inactive ? 0.22 : inHoldingState ? 0.33 : 0.26 : activeState == .inactive ? 0.1 : inHoldingState ? 0.27 : 0.2 : 0 ) + .saturation(isActiveTabGroup ? 1.0 : 0.0) // Highlight (if in dark mode) Color(.white) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index bd3e336028..f183d00e84 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -470,9 +470,9 @@ struct TabBarView: View { } label: { Image(systemName: "chevron.left") .controlSize(.regular) - .brightness( + .opacity( tabgroup.historyOffset == tabgroup.history.count-1 || tabgroup.history.isEmpty - ? -0.4 : 0.0 + ? 0.5 : 1.0 ) } primaryAction: { tabgroup.historyOffset += 1 @@ -497,7 +497,7 @@ struct TabBarView: View { } label: { Image(systemName: "chevron.right") .controlSize(.regular) - .brightness(tabgroup.historyOffset == 0 ? -0.4 : 0.0) + .opacity(tabgroup.historyOffset == 0 ? 0.5 : 1.0) } primaryAction: { tabgroup.historyOffset -= 1 } From b0d37dd5250e62328af34e2ad2788cf4d5546cd9 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 17:59:14 +0100 Subject: [PATCH 64/82] New splitted editor now has focus by default Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift | 1 + CodeEdit/Features/Tabs/Views/TabBarView.swift | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index cfe43c6321..a92c22f013 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -137,6 +137,7 @@ struct TabBarContextMenu: ViewModifier { let newTabGroup = TabGroupData(files: [item]) splitEditor(edge, newTabGroup) tabs.closeTab(item: item) + workspace.tabManager.activeTabGroup = newTabGroup } /// Copies the relative path from the workspace folder to the given file item to the pasteboard. diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index f183d00e84..ff662bba5b 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -571,11 +571,14 @@ struct TabBarView: View { } func split(edge: Edge) { + let newTabgroup: TabGroupData if let tab = tabgroup.selected { - splitEditor(edge, .init(files: [tab])) + newTabgroup = .init(files: [tab]) } else { - splitEditor(edge, .init()) + newTabgroup = .init() } + splitEditor(edge, newTabgroup) + tabManager.activeTabGroup = newTabgroup } private struct TabBarItemOnDropDelegate: DropDelegate { From 5392ac449cbc29770f710f98873702a91d3d3b1d Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 18:04:58 +0100 Subject: [PATCH 65/82] Empty tabgroup can get focus Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift index 0a657a83a7..428f5beb03 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift @@ -31,6 +31,11 @@ struct WorkspaceTabGroupView: View { .clipped() Spacer() } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .contentShape(Rectangle()) + .onTapGesture { + tabManager.activeTabGroup = tabgroup + } } } .frame(maxWidth: .infinity, maxHeight: .infinity) From dd1a35f6159dbd4a43f837114bda32aee69c4611 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 20:03:09 +0100 Subject: [PATCH 66/82] Fixed memory leak Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Models/TabManager.swift | 7 ++++--- CodeEdit/Features/Tabs/Views/TabBarView.swift | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CodeEdit/Features/Tabs/Models/TabManager.swift b/CodeEdit/Features/Tabs/Models/TabManager.swift index 3887bebf68..92747ca4d2 100644 --- a/CodeEdit/Features/Tabs/Models/TabManager.swift +++ b/CodeEdit/Features/Tabs/Models/TabManager.swift @@ -7,6 +7,7 @@ import Foundation import OrderedCollections +import DequeModule class TabManager: ObservableObject { /// Collection of all the tabgroups. @@ -15,19 +16,19 @@ class TabManager: ObservableObject { /// The TabGroup with active focus. @Published var activeTabGroup: TabGroupData { didSet { - activeTabGroupHistory.updateOrInsert(oldValue, at: 0) + activeTabGroupHistory.prepend { [weak oldValue] in oldValue } } } /// History of last-used tab groups. - var activeTabGroupHistory: OrderedSet = [] + var activeTabGroupHistory: Deque<() -> TabGroupData?> = [] var fileDocuments: [WorkspaceClient.FileItem: CodeFileDocument] = [:] init() { let tab = TabGroupData() self.activeTabGroup = tab - self.activeTabGroupHistory.append(tab) + self.activeTabGroupHistory.prepend { [weak tab] in tab } self.tabGroups = .horizontal(.init(.horizontal, tabgroups: [.one(tab)])) } diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index ff662bba5b..54a6868386 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -440,7 +440,8 @@ struct TabBarView: View { action: { tabgroup.close() if tabManager.activeTabGroup == tabgroup { - tabManager.activeTabGroup = tabManager.activeTabGroupHistory.removeFirst() + tabManager.activeTabGroupHistory.removeAll { $0() == nil } + tabManager.activeTabGroup = tabManager.activeTabGroupHistory.removeFirst()()! } tabManager.flatten() } From 3d3a2ad3e129e76096f03d42e9da3c0fd8ad6756 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 20:03:18 +0100 Subject: [PATCH 67/82] test for improved focus Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/EditorView.swift | 10 +++++++--- .../Tabs/TabGroup/WorkspaceTabGroupView.swift | 16 +++++++++------- CodeEdit/WorkspaceView.swift | 11 ++++++++++- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index fe7d7d316f..5f2e0a1dc0 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -18,10 +18,12 @@ struct EditorView: View { window.contentView?.safeAreaInsets.top ?? .zero } + @FocusState.Binding var focus: TabGroupData? + var body: some View { switch tabgroup { case .one(let detailTabGroup): - WorkspaceTabGroupView(tabgroup: detailTabGroup) + WorkspaceTabGroupView(tabgroup: detailTabGroup, focus: $focus) .transformEnvironment(\.edgeInsets) { insets in switch isAtEdge { case .all: @@ -36,13 +38,15 @@ struct EditorView: View { } } case .vertical(let data), .horizontal(let data): - SubEditorView(data: data) + SubEditorView(data: data, focus: $focus) } } struct SubEditorView: View { @ObservedObject var data: SplitViewData + @FocusState.Binding var focus: TabGroupData? + var body: some View { SplitView(axis: data.axis) { splitView @@ -52,7 +56,7 @@ struct EditorView: View { var splitView: some View { ForEach(Array(data.tabgroups.enumerated()), id: \.offset) { index, item in - EditorView(tabgroup: item) + EditorView(tabgroup: item, focus: $focus) .transformEnvironment(\.isAtEdge) { belowToolbar in calcIsAtEdge(current: &belowToolbar, index: index) } diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift index 428f5beb03..651856f30e 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift @@ -12,7 +12,7 @@ struct WorkspaceTabGroupView: View { @EnvironmentObject var tabManager: TabManager - @FocusState var isFocused + @FocusState.Binding var focus: TabGroupData? var body: some View { VStack { @@ -60,12 +60,14 @@ struct WorkspaceTabGroupView: View { .environment(\.isActiveTabGroup, tabgroup == tabManager.activeTabGroup) .background(EffectView(.titlebar, blendingMode: .withinWindow, emphasized: false)) } - .focused($isFocused) - .onChange(of: isFocused) { focused in - if focused { - tabManager.activeTabGroup = tabgroup - } - } + .focused($focus, equals: tabgroup) +// .focused($isFocused) +// .onChange(of: isFocused) { focused in +// print("Focus change for \(tabgroup.selected?.fileName)", focused) +// if focused { +// tabManager.activeTabGroup = tabgroup +// } +// } .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CodeEditor.didBeginEditing"))) { _ in print("Not temporary anymore") tabgroup.temporaryTab = nil diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 1050fe09da..c9050a1fc3 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -33,13 +33,15 @@ struct WorkspaceView: View { @State var terminalCollapsed = true + @FocusState var focusedEditor: TabGroupData? + var body: some View { if workspace.workspaceClient != nil { VStack { SplitViewReader { proxy in SplitView(axis: .vertical) { - EditorView(tabgroup: tabManager.tabGroups) + EditorView(tabgroup: tabManager.tabGroups, focus: $focusedEditor) .frame(maxWidth: .infinity, maxHeight: .infinity) .safeAreaInset(edge: .bottom, spacing: 0) { StatusBarView(proxy: proxy, collapsed: $terminalCollapsed) @@ -54,6 +56,13 @@ struct WorkspaceView: View { .edgesIgnoringSafeArea(.top) .environmentObject(workspace.statusBarModel) .frame(maxWidth: .infinity, maxHeight: .infinity) + .onChange(of: focusedEditor) { newValue in + if let newValue { + tabManager.activeTabGroup = newValue + } else { + print("No Editor has focus") + } + } } } } From 6a4f31365930c6bd2561046ba554808cea523648 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 20:05:49 +0100 Subject: [PATCH 68/82] other fix for focus Signed-off-by: Wouter01 --- CodeEdit/WorkspaceView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index c9050a1fc3..bfa7c69403 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -63,6 +63,11 @@ struct WorkspaceView: View { print("No Editor has focus") } } + .onChange(of: tabManager.activeTabGroup) { newValue in + if newValue != focusedEditor { + focusedEditor = newValue + } + } } } } From a7da95926dc750cd5d89ca95e20f2a3e388f3d61 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 22:34:10 +0100 Subject: [PATCH 69/82] new try for focus Signed-off-by: Wouter01 --- CodeEdit/Features/CodeFile/CodeFileView.swift | 4 ++- .../Tabs/TabGroup/WorkspaceTabGroupView.swift | 27 +++++++++++----- CodeEdit/WorkspaceView.swift | 31 ++++++++++++------- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/CodeEdit/Features/CodeFile/CodeFileView.swift b/CodeEdit/Features/CodeFile/CodeFileView.swift index 24643d873a..92b66ba0ce 100644 --- a/CodeEdit/Features/CodeFile/CodeFileView.swift +++ b/CodeEdit/Features/CodeFile/CodeFileView.swift @@ -62,6 +62,8 @@ struct CodeFileView: View { @Environment(\.edgeInsets) var edgeInsets + @EnvironmentObject var tabgroup: TabGroupData + var body: some View { CodeEditTextView( $codeFile.content, @@ -75,7 +77,7 @@ struct CodeFileView: View { useThemeBackground: prefs.preferences.theme.useThemeBackground, contentInsets: edgeInsets.nsEdgeInsets ) - .id(codeFile.fileURL) + .id("\(tabgroup.id)\(codeFile.fileURL)") .background { if colorScheme == .dark { if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedLightTheme { diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift index 651856f30e..6859b573b1 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift @@ -14,6 +14,8 @@ struct WorkspaceTabGroupView: View { @FocusState.Binding var focus: TabGroupData? + @FocusState var focused + var body: some View { VStack { if let selected = tabgroup.selected { @@ -60,14 +62,25 @@ struct WorkspaceTabGroupView: View { .environment(\.isActiveTabGroup, tabgroup == tabManager.activeTabGroup) .background(EffectView(.titlebar, blendingMode: .withinWindow, emphasized: false)) } - .focused($focus, equals: tabgroup) + .environmentObject(tabgroup) +// .focused($focus, equals: tabgroup) + .focused($focused) + +// .focused($focus, equals: tabgroup) + // .focused($isFocused) -// .onChange(of: isFocused) { focused in -// print("Focus change for \(tabgroup.selected?.fileName)", focused) -// if focused { -// tabManager.activeTabGroup = tabgroup -// } -// } + .task(id: tabManager.activeTabGroup) { + focused = tabManager.activeTabGroup == tabgroup + print("Set Focus to \(focused)") + } + .onChange(of: focused) { focused in + print("Focus change for \(tabgroup.selected?.fileName)", focused) + if focused { + tabManager.activeTabGroup = tabgroup + } else { + print("Lost focus") + } + } .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CodeEditor.didBeginEditing"))) { _ in print("Not temporary anymore") tabgroup.temporaryTab = nil diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index bfa7c69403..a8e9e83dfc 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -35,6 +35,10 @@ struct WorkspaceView: View { @FocusState var focusedEditor: TabGroupData? + @Namespace var namespace + + @Environment(\.resetFocus) var resetFocus + var body: some View { if workspace.workspaceClient != nil { VStack { @@ -53,21 +57,24 @@ struct WorkspaceView: View { .frame(minHeight: 200, maxHeight: 400) } + .edgesIgnoringSafeArea(.top) .environmentObject(workspace.statusBarModel) .frame(maxWidth: .infinity, maxHeight: .infinity) - .onChange(of: focusedEditor) { newValue in - if let newValue { - tabManager.activeTabGroup = newValue - } else { - print("No Editor has focus") - } - } - .onChange(of: tabManager.activeTabGroup) { newValue in - if newValue != focusedEditor { - focusedEditor = newValue - } - } +// .onChange(of: focusedEditor) { newValue in +// print(newValue?.selected?.fileName, newValue?.id) +// if let newValue { +// tabManager.activeTabGroup = newValue +// } else { +// print("No Editor has focus") +// } +// } +// .onChange(of: tabManager.activeTabGroup) { newValue in +// if newValue != focusedEditor { +// print("Syncing focus to", newValue.selected) +// focusedEditor = newValue +// } +// } } } } From edb8945f4e2105aeb124c9e81eb9972bb2e08a0c Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Mon, 6 Mar 2023 23:59:55 +0100 Subject: [PATCH 70/82] reverted changes Signed-off-by: Wouter01 --- CodeEdit/Features/CodeFile/CodeFileView.swift | 2 +- .../Tabs/TabGroup/WorkspaceTabGroupView.swift | 22 +-------------- CodeEdit/WorkspaceView.swift | 28 +++++++------------ 3 files changed, 12 insertions(+), 40 deletions(-) diff --git a/CodeEdit/Features/CodeFile/CodeFileView.swift b/CodeEdit/Features/CodeFile/CodeFileView.swift index 92b66ba0ce..8fd70ed18f 100644 --- a/CodeEdit/Features/CodeFile/CodeFileView.swift +++ b/CodeEdit/Features/CodeFile/CodeFileView.swift @@ -77,7 +77,7 @@ struct CodeFileView: View { useThemeBackground: prefs.preferences.theme.useThemeBackground, contentInsets: edgeInsets.nsEdgeInsets ) - .id("\(tabgroup.id)\(codeFile.fileURL)") + .id(codeFile.fileURL) .background { if colorScheme == .dark { if prefs.preferences.theme.selectedTheme == prefs.preferences.theme.selectedLightTheme { diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift index 6859b573b1..6704f28f63 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift @@ -14,8 +14,6 @@ struct WorkspaceTabGroupView: View { @FocusState.Binding var focus: TabGroupData? - @FocusState var focused - var body: some View { VStack { if let selected = tabgroup.selected { @@ -62,25 +60,7 @@ struct WorkspaceTabGroupView: View { .environment(\.isActiveTabGroup, tabgroup == tabManager.activeTabGroup) .background(EffectView(.titlebar, blendingMode: .withinWindow, emphasized: false)) } - .environmentObject(tabgroup) -// .focused($focus, equals: tabgroup) - .focused($focused) - -// .focused($focus, equals: tabgroup) - -// .focused($isFocused) - .task(id: tabManager.activeTabGroup) { - focused = tabManager.activeTabGroup == tabgroup - print("Set Focus to \(focused)") - } - .onChange(of: focused) { focused in - print("Focus change for \(tabgroup.selected?.fileName)", focused) - if focused { - tabManager.activeTabGroup = tabgroup - } else { - print("Lost focus") - } - } + .focused($focus, equals: tabgroup) .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CodeEditor.didBeginEditing"))) { _ in print("Not temporary anymore") tabgroup.temporaryTab = nil diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index a8e9e83dfc..ec47d7ccf9 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -35,10 +35,6 @@ struct WorkspaceView: View { @FocusState var focusedEditor: TabGroupData? - @Namespace var namespace - - @Environment(\.resetFocus) var resetFocus - var body: some View { if workspace.workspaceClient != nil { VStack { @@ -61,20 +57,16 @@ struct WorkspaceView: View { .edgesIgnoringSafeArea(.top) .environmentObject(workspace.statusBarModel) .frame(maxWidth: .infinity, maxHeight: .infinity) -// .onChange(of: focusedEditor) { newValue in -// print(newValue?.selected?.fileName, newValue?.id) -// if let newValue { -// tabManager.activeTabGroup = newValue -// } else { -// print("No Editor has focus") -// } -// } -// .onChange(of: tabManager.activeTabGroup) { newValue in -// if newValue != focusedEditor { -// print("Syncing focus to", newValue.selected) -// focusedEditor = newValue -// } -// } + .onChange(of: focusedEditor) { newValue in + if let newValue { + tabManager.activeTabGroup = newValue + } + } + .onChange(of: tabManager.activeTabGroup) { newValue in + if newValue != focusedEditor { + focusedEditor = newValue + } + } } } } From 7acd8bd87fac84c73ad06b47cdae367636252e5b Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Tue, 7 Mar 2023 00:04:49 +0100 Subject: [PATCH 71/82] code style fixes Signed-off-by: Wouter01 --- CodeEdit/Features/CodeFile/CodeFileView.swift | 6 ++++-- CodeEdit/Features/SplitView/EditorView.swift | 11 +++++++---- CodeEdit/Features/SplitView/SplitView.swift | 3 ++- .../Tabs/TabGroup/WorkspaceTabGroupView.swift | 9 ++++++--- CodeEdit/WorkspaceView.swift | 14 +++++++++----- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/CodeEdit/Features/CodeFile/CodeFileView.swift b/CodeEdit/Features/CodeFile/CodeFileView.swift index 8fd70ed18f..28051daf80 100644 --- a/CodeEdit/Features/CodeFile/CodeFileView.swift +++ b/CodeEdit/Features/CodeFile/CodeFileView.swift @@ -60,9 +60,11 @@ struct CodeFileView: View { return AppPreferencesModel.shared.preferences.textEditing.font.current() }() - @Environment(\.edgeInsets) var edgeInsets + @Environment(\.edgeInsets) + private var edgeInsets - @EnvironmentObject var tabgroup: TabGroupData + @EnvironmentObject + private var tabgroup: TabGroupData var body: some View { CodeEditTextView( diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index 5f2e0a1dc0..a786123fa6 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -10,16 +10,19 @@ import SwiftUI struct EditorView: View { var tabgroup: TabGroup - @Environment(\.window) private var window + @FocusState.Binding + var focus: TabGroupData? - @Environment(\.isAtEdge) private var isAtEdge + @Environment(\.window) + private var window + + @Environment(\.isAtEdge) + private var isAtEdge var toolbarHeight: CGFloat { window.contentView?.safeAreaInsets.top ?? .zero } - @FocusState.Binding var focus: TabGroupData? - var body: some View { switch tabgroup { case .one(let detailTabGroup): diff --git a/CodeEdit/Features/SplitView/SplitView.swift b/CodeEdit/Features/SplitView/SplitView.swift index 6655d1b061..9740699e4d 100644 --- a/CodeEdit/Features/SplitView/SplitView.swift +++ b/CodeEdit/Features/SplitView/SplitView.swift @@ -10,7 +10,8 @@ import SwiftUI struct SplitView: View { var content: Content - @State var viewController: SplitViewController + @State + var viewController: SplitViewController init(axis: Axis, @ViewBuilder content: () -> Content) { self.content = content() diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift index 6704f28f63..14aa457983 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift @@ -8,11 +8,14 @@ import SwiftUI struct WorkspaceTabGroupView: View { - @ObservedObject var tabgroup: TabGroupData + @ObservedObject + var tabgroup: TabGroupData - @EnvironmentObject var tabManager: TabManager + @FocusState.Binding + var focus: TabGroupData? - @FocusState.Binding var focus: TabGroupData? + @EnvironmentObject + private var tabManager: TabManager var body: some View { VStack { diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index ec47d7ccf9..975dfd304c 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -14,9 +14,10 @@ struct WorkspaceView: View { private var path: String = "" @EnvironmentObject - var workspace: WorkspaceDocument + private var workspace: WorkspaceDocument - @EnvironmentObject private var tabManager: TabManager + @EnvironmentObject + private var tabManager: TabManager @StateObject private var prefs: AppPreferencesModel = .shared @@ -29,11 +30,14 @@ struct WorkspaceView: View { @State private var showingAlert = false - @Environment(\.colorScheme) var colorScheme + @Environment(\.colorScheme) + private var colorScheme - @State var terminalCollapsed = true + @State + private var terminalCollapsed = true - @FocusState var focusedEditor: TabGroupData? + @FocusState + var focusedEditor: TabGroupData? var body: some View { if workspace.workspaceClient != nil { From 99111f820d657d6e692a174c9b26b182cc021eff Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Tue, 7 Mar 2023 00:09:17 +0100 Subject: [PATCH 72/82] Code style fixes Signed-off-by: Wouter01 --- CodeEdit.xcodeproj/project.pbxproj | 4 --- .../Views/BreadcrumbsComponent.swift | 4 ++- .../Views/WorkspaceCodeFileView.swift | 6 ++-- .../InspectorSidebarView.swift | 3 +- .../ExtensionNavigatorItemView.swift | 32 ------------------- .../ExtensionNavigatorView.swift | 4 --- .../StatusBarToggleDrawerButton.swift | 3 +- .../StatusBar/Views/StatusBarView.swift | 3 +- .../Features/Tabs/Views/TabBarItemView.swift | 3 +- CodeEdit/Features/Tabs/Views/TabBarView.swift | 3 +- 10 files changed, 17 insertions(+), 48 deletions(-) delete mode 100644 CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorItemView.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index cee8fe7162..4ea22a121e 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -18,7 +18,6 @@ 0483E35027FDB17700354AC0 /* ExtensionNavigatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0483E34F27FDB17700354AC0 /* ExtensionNavigatorView.swift */; }; 0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */; }; 04C3254B27FF23B000C8DA2D /* ExtensionNavigatorData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C3254A27FF23B000C8DA2D /* ExtensionNavigatorData.swift */; }; - 04C3254D27FF331B00C8DA2D /* ExtensionNavigatorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C3254C27FF331B00C8DA2D /* ExtensionNavigatorItemView.swift */; }; 04C3254F2800AA4700C8DA2D /* ExtensionInstallationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C3254E2800AA4700C8DA2D /* ExtensionInstallationView.swift */; }; 04C325512800AC7400C8DA2D /* ExtensionInstallationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C325502800AC7400C8DA2D /* ExtensionInstallationViewModel.swift */; }; 04C3255B2801F86400C8DA2D /* OutlineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285FEC6D27FE4B4A00E57D53 /* OutlineViewController.swift */; }; @@ -421,7 +420,6 @@ 0483E34F27FDB17700354AC0 /* ExtensionNavigatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionNavigatorView.swift; sourceTree = ""; }; 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceCodeFileView.swift; sourceTree = ""; }; 04C3254A27FF23B000C8DA2D /* ExtensionNavigatorData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionNavigatorData.swift; sourceTree = ""; }; - 04C3254C27FF331B00C8DA2D /* ExtensionNavigatorItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionNavigatorItemView.swift; sourceTree = ""; }; 04C3254E2800AA4700C8DA2D /* ExtensionInstallationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInstallationView.swift; sourceTree = ""; }; 04C325502800AC7400C8DA2D /* ExtensionInstallationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInstallationViewModel.swift; sourceTree = ""; }; 200412EE280F3EAC00BCAF5C /* HistoryInspectorNoHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryInspectorNoHistoryView.swift; sourceTree = ""; }; @@ -822,7 +820,6 @@ children = ( 0483E34F27FDB17700354AC0 /* ExtensionNavigatorView.swift */, 04C3254A27FF23B000C8DA2D /* ExtensionNavigatorData.swift */, - 04C3254C27FF331B00C8DA2D /* ExtensionNavigatorItemView.swift */, 04C3254E2800AA4700C8DA2D /* ExtensionInstallationView.swift */, 04C325502800AC7400C8DA2D /* ExtensionInstallationViewModel.swift */, ); @@ -2681,7 +2678,6 @@ 6C2C156129B4F52F00EA60A5 /* SplitViewModifiers.swift in Sources */, 0463E51327FCC1FB00806D5C /* CodeEditTargetsAPI.swift in Sources */, 201169DD2837B3AC00F92B46 /* SourceControlToolbarBottom.swift in Sources */, - 04C3254D27FF331B00C8DA2D /* ExtensionNavigatorItemView.swift in Sources */, 587B9E8B29301D8F00AC7927 /* GitHubAccount+deleteReference.swift in Sources */, D7E201B027E8C07300CB86D0 /* FindNavigatorSearchBar.swift in Sources */, 58798237292E30B90085B254 /* FeedbackView.swift in Sources */, diff --git a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift index 3e50d8df84..1ade9e7f58 100644 --- a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift +++ b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift @@ -60,7 +60,9 @@ struct BreadcrumbsComponent: View { } struct NSPopUpButtonView: NSViewRepresentable where ItemType: Equatable { - @Binding var selection: ItemType + @Binding + var selection: ItemType + var popupCreator: () -> NSPopUpButton typealias NSViewType = NSPopUpButton diff --git a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift b/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift index 246e6b4d37..89042f4592 100644 --- a/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift +++ b/CodeEdit/Features/Documents/Views/WorkspaceCodeFileView.swift @@ -10,9 +10,11 @@ import UniformTypeIdentifiers struct WorkspaceCodeFileView: View { - @EnvironmentObject private var tabManager: TabManager + @EnvironmentObject + private var tabManager: TabManager - @EnvironmentObject private var tabgroup: TabGroupData + @EnvironmentObject + private var tabgroup: TabGroupData var file: WorkspaceClient.FileItem diff --git a/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift b/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift index 226a4bef10..abbec01da0 100644 --- a/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift +++ b/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift @@ -12,7 +12,8 @@ struct InspectorSidebarView: View { @ObservedObject private var workspace: WorkspaceDocument - @EnvironmentObject private var tabManager: TabManager + @EnvironmentObject + private var tabManager: TabManager @State private var selection: Int = 0 diff --git a/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorItemView.swift b/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorItemView.swift deleted file mode 100644 index 1a52cff88b..0000000000 --- a/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorItemView.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// ExtensionNavigatorItem.swift -// CodeEdit -// -// Created by Pavel Kasila on 7.04.22. -// - -import SwiftUI - -struct ExtensionNavigatorItemView: View { - var plugin: Plugin - @EnvironmentObject var document: WorkspaceDocument - - var body: some View { - Button { -// document.openTab(item: plugin) - } label: { - ZStack { - HStack { - VStack(alignment: .leading) { - Text(plugin.manifest.displayName) - .font(.headline) - Text(plugin.manifest.name) - .font(.subheadline) - } - Spacer() - } - } - } - .buttonStyle(.plain) - } -} diff --git a/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorView.swift b/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorView.swift index df6b865a36..72956d28bb 100644 --- a/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorView.swift +++ b/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorView.swift @@ -18,10 +18,6 @@ struct ExtensionNavigatorView: View { VStack { Divider() // TODO: fix this workaround because when switching tabs without this, the app crashes List { - ForEach(workspace.extensionNavigatorData.plugins) { plugin in - ExtensionNavigatorItemView(plugin: plugin) - .tag(plugin) - } if !workspace.extensionNavigatorData.listFull { HStack { diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift index 3b71083b31..7408829798 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleDrawerButton.swift @@ -11,7 +11,8 @@ internal struct StatusBarToggleDrawerButton: View { @EnvironmentObject private var model: StatusBarViewModel - @Binding var collapsed: Bool + @Binding + var collapsed: Bool init(collapsed: Binding) { self._collapsed = collapsed diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index d302771c53..1b53a801f3 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -33,7 +33,8 @@ struct StatusBarView: View { var proxy: SplitViewProxy - @Binding var collapsed: Bool + @Binding + var collapsed: Bool static let statusbarID = "statusbarID" diff --git a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift index e9eb3868ef..30bdc1bca7 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarItemView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarItemView.swift @@ -23,7 +23,8 @@ struct TabBarItemView: View { @Environment(\.isFullscreen) private var isFullscreen - @EnvironmentObject var tabManager: TabManager + @EnvironmentObject + private var tabManager: TabManager /// User preferences. @StateObject diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 54a6868386..a36371d1bc 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -32,7 +32,8 @@ struct TabBarView: View { @EnvironmentObject private var workspace: WorkspaceDocument - @EnvironmentObject private var tabManager: TabManager + @EnvironmentObject + private var tabManager: TabManager @EnvironmentObject private var tabgroup: TabGroupData From b8472425aad04a56db8c81774ab907e9252d514e Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Tue, 7 Mar 2023 00:09:56 +0100 Subject: [PATCH 73/82] code style fix Signed-off-by: Wouter01 --- .../ProjectNavigator/OutlineView/OutlineView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineView.swift b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineView.swift index c055f83e1c..606b41e620 100644 --- a/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineView.swift +++ b/CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineView.swift @@ -18,7 +18,8 @@ struct OutlineView: NSViewControllerRepresentable { var prefs: AppPreferencesModel = .shared // This is mainly just used to trigger a view update. - @Binding var selection: WorkspaceClient.FileItem? + @Binding + var selection: WorkspaceClient.FileItem? typealias NSViewControllerType = OutlineViewController From b58551abb8dc2cb3ef89ddf83bb81b63e7213aa3 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Tue, 7 Mar 2023 00:14:19 +0100 Subject: [PATCH 74/82] Code style fixes Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/SplitViewControllerView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Features/SplitView/SplitViewControllerView.swift b/CodeEdit/Features/SplitView/SplitViewControllerView.swift index 2cc909ec93..65953e8514 100644 --- a/CodeEdit/Features/SplitView/SplitViewControllerView.swift +++ b/CodeEdit/Features/SplitView/SplitViewControllerView.swift @@ -13,7 +13,6 @@ struct SplitViewControllerView: NSViewControllerRepresentable { var viewController: SplitViewController func makeNSViewController(context: Context) -> SplitViewController { -// viewController.items = children.map { SplitViewItem(child: $0) } return viewController } @@ -37,9 +36,11 @@ struct SplitViewControllerView: NSViewControllerRepresentable { } controller.splitViewItems = controller.items.map(\.item) + if hasChanged && controller.splitViewItems.count > 1 { let splitView = controller.splitView let numerator = splitView.isVertical ? splitView.frame.width : splitView.frame.height + for idx in 0.. Date: Tue, 7 Mar 2023 00:33:52 +0100 Subject: [PATCH 75/82] Added docs Signed-off-by: Wouter01 --- .../Features/SplitView/SplitViewControllerView.swift | 2 +- CodeEdit/Features/SplitView/SplitViewData.swift | 9 +++++++++ CodeEdit/Features/SplitView/SplitViewItem.swift | 5 ++++- CodeEdit/Features/SplitView/SplitViewReader.swift | 10 ++++++++++ CodeEdit/Features/SplitView/Variadic.swift | 2 ++ CodeEdit/Features/Tabs/Models/TabManager.swift | 6 +++++- CodeEdit/Features/Tabs/TabGroup/TabGroup.swift | 5 +++++ CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift | 12 ++++++++++++ 8 files changed, 48 insertions(+), 3 deletions(-) diff --git a/CodeEdit/Features/SplitView/SplitViewControllerView.swift b/CodeEdit/Features/SplitView/SplitViewControllerView.swift index 65953e8514..e9ab1585f6 100644 --- a/CodeEdit/Features/SplitView/SplitViewControllerView.swift +++ b/CodeEdit/Features/SplitView/SplitViewControllerView.swift @@ -20,7 +20,7 @@ struct SplitViewControllerView: NSViewControllerRepresentable { updateItems(controller: controller) } - func updateItems(controller: SplitViewController) { + private func updateItems(controller: SplitViewController) { var hasChanged = false // Reorder viewcontrollers if needed and add new ones. controller.items = children.map { child in diff --git a/CodeEdit/Features/SplitView/SplitViewData.swift b/CodeEdit/Features/SplitView/SplitViewData.swift index 45f6c444e6..39bf893b13 100644 --- a/CodeEdit/Features/SplitView/SplitViewData.swift +++ b/CodeEdit/Features/SplitView/SplitViewData.swift @@ -24,6 +24,12 @@ class SplitViewData: ObservableObject { } /// Splits the editor at a certain index into two separate editors. + /// - Parameters: + /// - direction: direction in which the editor will be split. + /// If the direction is the same as the ancestor direction, + /// the editor is added to the ancestor instead of creating a new split container. + /// - index: index where the divider will be added. + /// - tabgroup: new tabgroup class that will be used for the editor. func split(_ direction: Edge, at index: Int, new tabgroup: TabGroupData) { tabgroup.parent = self switch (axis, direction) { @@ -47,6 +53,9 @@ class SplitViewData: ObservableObject { } } + + /// Closes a TabGroup. + /// - Parameter id: ID of the TabGroup. func closeTabGroup(with id: TabGroupData.ID) { tabgroups.removeAll { tabgroup in if case .one(let tabGroupData) = tabgroup { diff --git a/CodeEdit/Features/SplitView/SplitViewItem.swift b/CodeEdit/Features/SplitView/SplitViewItem.swift index 370c332916..f8b2388b72 100644 --- a/CodeEdit/Features/SplitView/SplitViewItem.swift +++ b/CodeEdit/Features/SplitView/SplitViewItem.swift @@ -28,7 +28,7 @@ class SplitViewItem: ObservableObject { self.observers = createObservers() } - func createObservers() -> [NSKeyValueObservation] { + private func createObservers() -> [NSKeyValueObservation] { [ item.observe(\.isCollapsed) { item, _ in self.collapsed.wrappedValue = item.isCollapsed @@ -36,6 +36,9 @@ class SplitViewItem: ObservableObject { ] } + /// Updates a SplitViewItem. + /// This will fetch updated binding values and update them if needed. + /// - Parameter child: the view corresponding to the SplitViewItem. func update(child: _VariadicView.Children.Element) { self.item.canCollapse = child[SplitViewItemCanCollapseViewTraitKey.self] DispatchQueue.main.async { diff --git a/CodeEdit/Features/SplitView/SplitViewReader.swift b/CodeEdit/Features/SplitView/SplitViewReader.swift index c4f6a4b06d..faba280ba2 100644 --- a/CodeEdit/Features/SplitView/SplitViewReader.swift +++ b/CodeEdit/Features/SplitView/SplitViewReader.swift @@ -41,10 +41,20 @@ struct SplitViewProxy { self.viewController = viewController } + /// Set the position of a divider in a splitview. + /// - Parameters: + /// - index: index of the divider. The mostleft / top divider has index 0. + /// - position: position to place the divider. This is a position inside the views width / height. + /// For example, if the splitview has a width of 500, setting the position to 250 + /// will put the divider in the middle of the splitview. func setPosition(of index: Int, position: CGFloat) { viewController()?.splitView.setPosition(position, ofDividerAt: index) } + /// Collapse a view of the splitview. + /// - Parameters: + /// - id: ID of the view + /// - enabled: true for collapse. func collapseView(with id: AnyHashable, _ enabled: Bool) { viewController()?.collapse(for: id, enabled: enabled) } diff --git a/CodeEdit/Features/SplitView/Variadic.swift b/CodeEdit/Features/SplitView/Variadic.swift index 16875b61f8..d3b7f94a1b 100644 --- a/CodeEdit/Features/SplitView/Variadic.swift +++ b/CodeEdit/Features/SplitView/Variadic.swift @@ -17,6 +17,8 @@ struct Helper: _VariadicView_UnaryViewRoot { } extension View { + + /// Exposes the children of a ViewBuilder so they can be accessed individually. func variadic(@ViewBuilder process: @escaping (_VariadicView.Children) -> R) -> some View { _VariadicView.Tree(Helper(_body: process), content: { self }) } diff --git a/CodeEdit/Features/Tabs/Models/TabManager.swift b/CodeEdit/Features/Tabs/Models/TabManager.swift index 92747ca4d2..4eb770ae57 100644 --- a/CodeEdit/Features/Tabs/Models/TabManager.swift +++ b/CodeEdit/Features/Tabs/Models/TabManager.swift @@ -39,7 +39,11 @@ class TabManager: ObservableObject { } } - /// Opens a new tab in a tabgroup. If no tabgroup is given, it is added to the active tab group. + + /// Opens a new tab in a tabgroup. + /// - Parameters: + /// - item: The tab to open. + /// - tabgroup: The tabgroup to add the tab to. If nil, it is added to the active tab group. func openTab(item: WorkspaceClient.FileItem, in tabgroup: TabGroupData? = nil) { let tabgroup = tabgroup ?? activeTabGroup tabgroup.openTab(item: item) diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift index 60d8676d7f..f5d840505d 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift @@ -12,6 +12,8 @@ enum TabGroup { case vertical(SplitViewData) case horizontal(SplitViewData) + /// Closes all tabs which present the given file + /// - Parameter file: a file. func closeAllTabs(of file: WorkspaceClient.FileItem) { switch self { case .one(let tabGroupData): @@ -23,7 +25,10 @@ enum TabGroup { } } + /// Returns some tabgroup, except the given tabgroup. + /// - Parameter except: the search will exclude this tabgroup. + /// - Returns: Some tabgroup. func findSomeTabGroup(except: TabGroupData? = nil) -> TabGroupData? { switch self { case .one(let tabGroupData) where tabGroupData != except: diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index 2b41190d5f..d86d3d7ae4 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -78,6 +78,8 @@ final class TabGroupData: ObservableObject, Identifiable { } /// Closes a tab in the tabgroup. + /// This will also write any changes to the file on disk and will add the tab to the tab history. + /// - Parameter item: the tab to close. func closeTab(item: Tab) { historyOffset = 0 if item != selected { @@ -107,6 +109,7 @@ final class TabGroupData: ObservableObject, Identifiable { } } + // TODO: Fix state // if openedTabsFromState { // var openTabsInState = self.getFromWorkspaceState(key: openTabsStateName) as? [String] ?? [] // if let index = openTabsInState.firstIndex(of: item.url.absoluteString) { @@ -116,8 +119,12 @@ final class TabGroupData: ObservableObject, Identifiable { // } } + /// Opens a tab in the tabgroup. /// If a tab for the item already exists, it is used instead. + /// - Parameters: + /// - item: the tab to open. + /// - asTemporary: indicates whether the tab should be opened as a temporary tab or a permanent tab. func openTab(item: Tab, asTemporary: Bool) { // Item is already opened in a tab. guard !tabs.contains(item) || !asTemporary else { @@ -157,7 +164,12 @@ final class TabGroupData: ObservableObject, Identifiable { } } + /// Opens a tab in the tabgroup. + /// - Parameters: + /// - item: The tab to open. + /// - index: Index where the tab needs to be added. If nil, it is added to the back. + /// - fromHistory: Indicates whether the tab has been opened from going back in history. func openTab(item: Tab, at index: Int? = nil, fromHistory: Bool = false) { if let index { tabs.insert(item, at: index) From b742551c9d752726b18a1e9c1572d98960cf3cac Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Tue, 7 Mar 2023 00:35:21 +0100 Subject: [PATCH 76/82] Fixed scroll to active tab on editor width change Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarView.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index a36371d1bc..25ac41d680 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -387,6 +387,9 @@ struct TabBarView: View { // When window size changes, re-compute the expected tab width. .onChange(of: geometryProxy.size.width) { _ in updateExpectedTabWidth(proxy: geometryProxy) + withAnimation { + scrollReader.scrollTo(tabgroup.selected?.id) + } } // When user is not hovering anymore, re-compute the expected tab width immediately. .onHover { isHovering in From 120e3a7e1d6222ab16ef65e96519138fb506cc76 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Tue, 7 Mar 2023 00:39:48 +0100 Subject: [PATCH 77/82] Fixed warnings Signed-off-by: Wouter01 --- CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift | 2 +- CodeEdit/Features/SplitView/SplitViewData.swift | 1 - CodeEdit/Features/Tabs/Models/TabManager.swift | 1 - CodeEdit/Features/Tabs/TabGroup/TabGroup.swift | 1 - CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift | 2 -- 5 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift index 1ade9e7f58..efc9d44591 100644 --- a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift +++ b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsComponent.swift @@ -62,7 +62,7 @@ struct BreadcrumbsComponent: View { struct NSPopUpButtonView: NSViewRepresentable where ItemType: Equatable { @Binding var selection: ItemType - + var popupCreator: () -> NSPopUpButton typealias NSViewType = NSPopUpButton diff --git a/CodeEdit/Features/SplitView/SplitViewData.swift b/CodeEdit/Features/SplitView/SplitViewData.swift index 39bf893b13..b66715757e 100644 --- a/CodeEdit/Features/SplitView/SplitViewData.swift +++ b/CodeEdit/Features/SplitView/SplitViewData.swift @@ -53,7 +53,6 @@ class SplitViewData: ObservableObject { } } - /// Closes a TabGroup. /// - Parameter id: ID of the TabGroup. func closeTabGroup(with id: TabGroupData.ID) { diff --git a/CodeEdit/Features/Tabs/Models/TabManager.swift b/CodeEdit/Features/Tabs/Models/TabManager.swift index 4eb770ae57..8ff36c76ac 100644 --- a/CodeEdit/Features/Tabs/Models/TabManager.swift +++ b/CodeEdit/Features/Tabs/Models/TabManager.swift @@ -39,7 +39,6 @@ class TabManager: ObservableObject { } } - /// Opens a new tab in a tabgroup. /// - Parameters: /// - item: The tab to open. diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift index f5d840505d..1e005ec089 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroup.swift @@ -25,7 +25,6 @@ enum TabGroup { } } - /// Returns some tabgroup, except the given tabgroup. /// - Parameter except: the search will exclude this tabgroup. /// - Returns: Some tabgroup. diff --git a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift index d86d3d7ae4..f4cebbdd83 100644 --- a/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift +++ b/CodeEdit/Features/Tabs/TabGroup/TabGroupData.swift @@ -119,7 +119,6 @@ final class TabGroupData: ObservableObject, Identifiable { // } } - /// Opens a tab in the tabgroup. /// If a tab for the item already exists, it is used instead. /// - Parameters: @@ -164,7 +163,6 @@ final class TabGroupData: ObservableObject, Identifiable { } } - /// Opens a tab in the tabgroup. /// - Parameters: /// - item: The tab to open. From 8362f49264bf5d988bed78bf8bea2e888972c997 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 8 Mar 2023 00:24:56 +0100 Subject: [PATCH 78/82] Fix for focus Signed-off-by: Wouter01 --- CodeEdit/Features/SplitView/EditorView.swift | 36 +++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/CodeEdit/Features/SplitView/EditorView.swift b/CodeEdit/Features/SplitView/EditorView.swift index a786123fa6..e1ce901070 100644 --- a/CodeEdit/Features/SplitView/EditorView.swift +++ b/CodeEdit/Features/SplitView/EditorView.swift @@ -24,24 +24,26 @@ struct EditorView: View { } var body: some View { - switch tabgroup { - case .one(let detailTabGroup): - WorkspaceTabGroupView(tabgroup: detailTabGroup, focus: $focus) - .transformEnvironment(\.edgeInsets) { insets in - switch isAtEdge { - case .all: - insets.top += toolbarHeight - insets.bottom += StatusBarView.height + 5 - case .top: - insets.top += toolbarHeight - case .bottom: - insets.bottom += StatusBarView.height + 5 - default: - return + VStack { + switch tabgroup { + case .one(let detailTabGroup): + WorkspaceTabGroupView(tabgroup: detailTabGroup, focus: $focus) + .transformEnvironment(\.edgeInsets) { insets in + switch isAtEdge { + case .all: + insets.top += toolbarHeight + insets.bottom += StatusBarView.height + 5 + case .top: + insets.top += toolbarHeight + case .bottom: + insets.bottom += StatusBarView.height + 5 + default: + return + } } - } - case .vertical(let data), .horizontal(let data): - SubEditorView(data: data, focus: $focus) + case .vertical(let data), .horizontal(let data): + SubEditorView(data: data, focus: $focus) + } } } From 7fd88496d30638482c28c526726cfc6c4aa01aad Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 8 Mar 2023 23:10:05 +0100 Subject: [PATCH 79/82] Fixed issues regarding tabbarview Signed-off-by: Wouter01 --- .../Tabs/Views/TabBarContextMenu.swift | 27 ++++++++++--------- CodeEdit/Features/Tabs/Views/TabBarView.swift | 8 +++++- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift index a92c22f013..f1ff9d074c 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarContextMenu.swift @@ -38,18 +38,6 @@ struct TabBarContextMenu: ViewModifier { func body(content: Content) -> some View { content.contextMenu(menuItems: { Group { - Button("Split Up") { - moveToNewSplit(.top) - } - Button("Split Down") { - moveToNewSplit(.bottom) - } - Button("Split Left") { - moveToNewSplit(.leading) - } - Button("Split Right") { - moveToNewSplit(.trailing) - } Button("Close Tab") { withAnimation { tabs.closeTab(item: item) @@ -121,6 +109,21 @@ struct TabBarContextMenu: ViewModifier { } .disabled(true) } + + Divider() + + Button("Split Up") { + moveToNewSplit(.top) + } + Button("Split Down") { + moveToNewSplit(.bottom) + } + Button("Split Left") { + moveToNewSplit(.leading) + } + Button("Split Right") { + moveToNewSplit(.trailing) + } }) } diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 25ac41d680..4464204bd9 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -411,7 +411,11 @@ struct TabBarView: View { } } } - + .background { + if prefs.preferences.general.tabBarStyle == .native { + TabBarAccessoryNativeBackground(dividerAt: .none) + } + } } // Tab bar tools (e.g. split view). trailingAccessories @@ -464,6 +468,7 @@ struct TabBarView: View { id: \.offset ) { index, tab in Button { + tabManager.activeTabGroup = tabgroup tabgroup.historyOffset += index + 1 } label: { HStack { @@ -491,6 +496,7 @@ struct TabBarView: View { id: \.offset ) { index, tab in Button { + tabManager.activeTabGroup = tabgroup tabgroup.historyOffset -= index + 1 } label: { HStack { From be9de5c2d59c87fdbbf0c07a7f027290f1abed6e Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 8 Mar 2023 23:17:37 +0100 Subject: [PATCH 80/82] Fixed minor issues Signed-off-by: Wouter01 --- .../Features/Breadcrumbs/Views/BreadcrumbsView.swift | 3 +-- .../InspectorSidebar/InspectorSidebarView.swift | 5 ++++- .../ExtensionNavigator/ExtensionNavigatorView.swift | 1 - .../Features/StatusBar/Views/StatusBarView.swift | 12 ------------ .../Tabs/TabGroup/WorkspaceTabGroupView.swift | 1 - CodeEdit/Features/Tabs/Views/TabBarView.swift | 2 +- 6 files changed, 6 insertions(+), 18 deletions(-) diff --git a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift index e157910f4b..1ed08bcf32 100644 --- a/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift +++ b/CodeEdit/Features/Breadcrumbs/Views/BreadcrumbsView.swift @@ -58,8 +58,7 @@ struct BreadcrumbsView: View { } .frame(height: Self.height, alignment: .center) .opacity(activeState == .inactive ? 0.8 : 1.0) - .saturation(isActiveTabGroup ? 1.0 : 0.0) - .brightness(isActiveTabGroup ? 0.0 : -0.1) + .grayscale(isActiveTabGroup ? 0.0 : 1.0) .background(EffectView(.headerView).frame(height: Self.height)) } diff --git a/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift b/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift index abbec01da0..718dab1db3 100644 --- a/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift +++ b/CodeEdit/Features/InspectorSidebar/InspectorSidebarView.swift @@ -38,8 +38,11 @@ struct InspectorSidebarView: View { ) case 2: QuickHelpInspectorView().padding(5) - default: EmptyView() + default: + NoSelectionInspectorView() } + } else { + NoSelectionInspectorView() } } .frame( diff --git a/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorView.swift b/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorView.swift index 72956d28bb..0e4568ea4a 100644 --- a/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorView.swift +++ b/CodeEdit/Features/NavigatorSidebar/ExtensionNavigator/ExtensionNavigatorView.swift @@ -18,7 +18,6 @@ struct ExtensionNavigatorView: View { VStack { Divider() // TODO: fix this workaround because when switching tabs without this, the app crashes List { - if !workspace.extensionNavigatorData.listFull { HStack { Spacer() diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift index 1b53a801f3..0e3c5eb10c 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarView.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarView.swift @@ -79,7 +79,6 @@ struct StatusBarView: View { extension View { func cursor(_ cursor: NSCursor) -> some View { - // Using onContinuousHover instead of onHover, as the latter is less reliable. onHover { if $0 { cursor.push() @@ -87,16 +86,5 @@ extension View { cursor.pop() } } -// onContinuousHover { phase in -// print(phase) -// switch phase { -// case .active: -// cursor.push() -// case .ended: -// while NSCursor.current == cursor { -// cursor.pop() -// } -// } -// } } } diff --git a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift index 14aa457983..c97c04c83d 100644 --- a/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift +++ b/CodeEdit/Features/Tabs/TabGroup/WorkspaceTabGroupView.swift @@ -65,7 +65,6 @@ struct WorkspaceTabGroupView: View { } .focused($focus, equals: tabgroup) .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CodeEditor.didBeginEditing"))) { _ in - print("Not temporary anymore") tabgroup.temporaryTab = nil } } diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index 4464204bd9..ff9d77c8e0 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -524,7 +524,7 @@ struct TabBarView: View { .foregroundColor(.secondary) .buttonStyle(.plain) .padding(.horizontal, 7) - .brightness(activeState == .inactive ? -0.3 : 0) + .opacity(activeState != .inactive ? 1.0 : 0.5) .frame(maxHeight: .infinity) // Fill out vertical spaces. .background { if prefs.preferences.general.tabBarStyle == .native { From 38546869a8bfed6c1edbceafb61f13ffe1aa20bc Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 8 Mar 2023 23:20:05 +0100 Subject: [PATCH 81/82] Fixed focus issue for history buttons Signed-off-by: Wouter01 --- CodeEdit/Features/Tabs/Views/TabBarView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CodeEdit/Features/Tabs/Views/TabBarView.swift b/CodeEdit/Features/Tabs/Views/TabBarView.swift index ff9d77c8e0..e35b74c6a8 100644 --- a/CodeEdit/Features/Tabs/Views/TabBarView.swift +++ b/CodeEdit/Features/Tabs/Views/TabBarView.swift @@ -485,6 +485,7 @@ struct TabBarView: View { ? 0.5 : 1.0 ) } primaryAction: { + tabManager.activeTabGroup = tabgroup tabgroup.historyOffset += 1 } .disabled(tabgroup.historyOffset == tabgroup.history.count-1 || tabgroup.history.isEmpty) @@ -510,6 +511,7 @@ struct TabBarView: View { .controlSize(.regular) .opacity(tabgroup.historyOffset == 0 ? 0.5 : 1.0) } primaryAction: { + tabManager.activeTabGroup = tabgroup tabgroup.historyOffset -= 1 } .disabled(tabgroup.historyOffset == 0) From 31aa3c27ff2557ef32bea7028002e5255abd3b12 Mon Sep 17 00:00:00 2001 From: Wouter01 Date: Wed, 8 Mar 2023 23:53:31 +0100 Subject: [PATCH 82/82] removed transparent titlebar for native tab style Signed-off-by: Wouter01 --- .../Documents/Controllers/CodeEditWindowController.swift | 2 -- CodeEdit/WindowObserver.swift | 2 -- 2 files changed, 4 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 604779ce2e..84e7b99b22 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -121,12 +121,10 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate { if prefs.preferences.general.tabBarStyle == .native { // Set titlebar background as transparent by default in order to // style the toolbar background in native tab bar style. - self.window?.titlebarAppearsTransparent = true self.window?.titlebarSeparatorStyle = .none } else { // In xcode tab bar style, we use default toolbar background with // line separator. - self.window?.titlebarAppearsTransparent = false self.window?.titlebarSeparatorStyle = .automatic } self.window?.toolbar = toolbar diff --git a/CodeEdit/WindowObserver.swift b/CodeEdit/WindowObserver.swift index 561fa76a67..7f7f5e6d12 100644 --- a/CodeEdit/WindowObserver.swift +++ b/CodeEdit/WindowObserver.swift @@ -42,10 +42,8 @@ struct WindowObserver: View { .onChange(of: prefs.preferences.general.tabBarStyle) { newStyle in DispatchQueue.main.async { if newStyle == .native { - window.titlebarAppearsTransparent = true window.titlebarSeparatorStyle = .none } else { - window.titlebarAppearsTransparent = false window.titlebarSeparatorStyle = .automatic } }