From 66723592e2a26b1e4c593339eb1b8e7da4189662 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Tue, 12 Mar 2024 10:36:11 -0500 Subject: [PATCH] Navigation style setting and file history navigation improvements (#1603) * Added navigation style setting * Added navigation style setting * If one editors tab bar should be shown, all tab bars should be shown across other editors * Removed complete TODO comment * When there is only one tab in an editor and the navigation style changes to open in place, we change the tab to be termporary so that tab bar will hide * Invalidating flattenedEditors cache when editors change. * New editors open first tab as a temporary tab. * History improvements - if going back or forward in history to a file for a teb that was previously closed, we now open it as a temporary tab * Update CodeEditSourceEditor dependency version * Invalidating flattenedEditors cache when closing an editor * Removing last change * Fixed PR issue. --- CodeEdit.xcodeproj/project.pbxproj | 36 +++++++++-- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../CodeEditUI/Views/SegmentedControl.swift | 2 +- CodeEdit/Features/Editor/Models/Editor.swift | 22 ++++--- .../Features/Editor/Models/EditorLayout.swift | 18 ++++++ .../Editor/Models/EditorManager.swift | 29 ++++++++- .../PathBar/Views/EditorPathBarView.swift | 17 +++++- .../EditorTabBarLeadingAccessories.swift | 23 +++----- .../EditorTabBarTrailingAccessories.swift | 3 +- .../Features/Editor/Views/EditorView.swift | 59 +++++++++++++------ .../ProjectNavigatorViewController.swift | 3 +- .../Views/SourceControlNavigatorView.swift | 2 +- .../Settings/Models/SettingsData.swift | 7 ++- .../Models/NavigationSettings.swift | 42 +++++++++++++ .../NavigationSettingsView.swift | 32 ++++++++++ CodeEdit/Features/Settings/SettingsView.swift | 9 +++ .../SplitView/Model/SplitViewData.swift | 9 +++ 17 files changed, 259 insertions(+), 58 deletions(-) create mode 100644 CodeEdit/Features/Settings/Pages/NavigationSettings/Models/NavigationSettings.swift create mode 100644 CodeEdit/Features/Settings/Pages/NavigationSettings/NavigationSettingsView.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index df34db92ca..b0cbe368aa 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -422,6 +422,8 @@ B65B10FE2B08B07D002852CF /* SourceControlNavigatorChangesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B10FD2B08B07D002852CF /* SourceControlNavigatorChangesList.swift */; }; B65B11012B09D5D4002852CF /* GitClient+Pull.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B11002B09D5D4002852CF /* GitClient+Pull.swift */; }; B65B11042B09DB1C002852CF /* GitClient+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B11032B09DB1C002852CF /* GitClient+Fetch.swift */; }; + B664C3B02B965F6C00816B4E /* NavigationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B664C3AF2B965F6C00816B4E /* NavigationSettings.swift */; }; + B664C3B32B96634F00816B4E /* NavigationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B664C3B22B96634F00816B4E /* NavigationSettingsView.swift */; }; B66A4E4529C8E86D004573B4 /* CommandsFixes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E4429C8E86D004573B4 /* CommandsFixes.swift */; }; B66A4E4C29C9179B004573B4 /* CodeEditApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E4B29C9179B004573B4 /* CodeEditApp.swift */; }; B66A4E4F29C917B8004573B4 /* WelcomeWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E4E29C917B8004573B4 /* WelcomeWindow.swift */; }; @@ -953,6 +955,8 @@ B65B10FD2B08B07D002852CF /* SourceControlNavigatorChangesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorChangesList.swift; sourceTree = ""; }; B65B11002B09D5D4002852CF /* GitClient+Pull.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Pull.swift"; sourceTree = ""; }; B65B11032B09DB1C002852CF /* GitClient+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Fetch.swift"; sourceTree = ""; }; + B664C3AF2B965F6C00816B4E /* NavigationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSettings.swift; sourceTree = ""; }; + B664C3B22B96634F00816B4E /* NavigationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSettingsView.swift; sourceTree = ""; }; B66A4E4429C8E86D004573B4 /* CommandsFixes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandsFixes.swift; sourceTree = ""; }; B66A4E4B29C9179B004573B4 /* CodeEditApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditApp.swift; sourceTree = ""; }; B66A4E4E29C917B8004573B4 /* WelcomeWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeWindow.swift; sourceTree = ""; }; @@ -1218,13 +1222,13 @@ 287776EA27E350A100D46668 /* NavigatorArea */ = { isa = PBXGroup; children = ( - 307AC4CB2ABABD9800163376 /* ViewModels */, D7012EE627E757660001E1EF /* FindNavigator */, 581550CB29FBD30400684881 /* OutlineView */, 286471AC27ED52950039369D /* ProjectNavigator */, 201169D52837B29600F92B46 /* SourceControlNavigator */, B67660682AA972D400CD56B0 /* Models */, B67660692AA972DC00CD56B0 /* Views */, + 307AC4CB2ABABD9800163376 /* ViewModels */, ); path = NavigatorArea; sourceTree = ""; @@ -2494,16 +2498,17 @@ B61DA9DD29D929BF00BF4A43 /* Pages */ = { isa = PBXGroup; children = ( + B664C3AD2B965F4500816B4E /* NavigationSettings */, + B61DA9E129D929F900BF4A43 /* GeneralSettings */, + B6E41C6E29DD15540088F9F4 /* AccountsSettings */, + 58F2EAAE292FB2B0004A9BDE /* ThemeSettings */, + B6EA1FF329DA37D3001BF195 /* TextEditingSettings */, 5B698A082B262F8400DE9392 /* SearchSettings */, 6C5BE51A2A3D5419002DA0FC /* FeatureFlags */, B6CF632629E5417C0085880A /* Keybindings */, - B6E41C6E29DD15540088F9F4 /* AccountsSettings */, - B6EA1FF329DA37D3001BF195 /* TextEditingSettings */, B6F0516E29D9E35300D72287 /* LocationsSettings */, B6F0516D29D9E34200D72287 /* SourceControlSettings */, B6F0516C29D9E32700D72287 /* TerminalSettings */, - 58F2EAAE292FB2B0004A9BDE /* ThemeSettings */, - B61DA9E129D929F900BF4A43 /* GeneralSettings */, ); path = Pages; sourceTree = ""; @@ -2604,6 +2609,23 @@ path = "Preview Content"; sourceTree = ""; }; + B664C3AD2B965F4500816B4E /* NavigationSettings */ = { + isa = PBXGroup; + children = ( + B664C3AE2B965F5500816B4E /* Models */, + B664C3B22B96634F00816B4E /* NavigationSettingsView.swift */, + ); + path = NavigationSettings; + sourceTree = ""; + }; + B664C3AE2B965F5500816B4E /* Models */ = { + isa = PBXGroup; + children = ( + B664C3AF2B965F6C00816B4E /* NavigationSettings.swift */, + ); + path = Models; + sourceTree = ""; + }; B66DD19E2B3C0E0C0004FFEC /* History */ = { isa = PBXGroup; children = ( @@ -3215,6 +3237,7 @@ 587B9E9229301D8F00AC7927 /* BitBucketAccount.swift in Sources */, DE513F52281B672D002260B9 /* EditorTabBarAccessory.swift in Sources */, 2813F93927ECC4C300E305E4 /* NavigatorAreaView.swift in Sources */, + B664C3B02B965F6C00816B4E /* NavigationSettings.swift in Sources */, 5B698A0F2B2636A700DE9392 /* SearchSettingsIgnoreGlobPatternItemView.swift in Sources */, 587B9E8A29301D8F00AC7927 /* GitHubIssue.swift in Sources */, EC0870F72A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift in Sources */, @@ -3263,6 +3286,7 @@ 611192042B08CCED00D4459B /* SearchIndexer+ProgressiveSearch.swift in Sources */, 611192022B08CCDC00D4459B /* SearchIndexer+Search.swift in Sources */, 04BA7C272AE2E9F100584E1C /* GitClient+Push.swift in Sources */, + B664C3B32B96634F00816B4E /* NavigationSettingsView.swift in Sources */, 2B7A583527E4BA0100D25D4E /* AppDelegate.swift in Sources */, D7012EE827E757850001E1EF /* FindNavigatorView.swift in Sources */, 58A5DF8029325B5A00D1BD5D /* GitClient.swift in Sources */, @@ -4593,7 +4617,7 @@ repositoryURL = "https://github.com/CodeEditApp/CodeEditSourceEditor"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.7.1; + minimumVersion = 0.7.2; }; }; 6CDEFC9429E22C2700B7C684 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8471683451..040dc189fd 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/CodeEditApp/CodeEditSourceEditor", "state" : { - "revision" : "a72e6c9b5f38b00ea0b03da5e706e3c4d05c706e", - "version" : "0.7.1" + "revision" : "7360f00bf7ec8e93b4833357bd254bef7e5c943d", + "version" : "0.7.2" } }, { diff --git a/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift b/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift index 05b97e270f..0b142e54e1 100644 --- a/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift +++ b/CodeEdit/Features/CodeEditUI/Views/SegmentedControl.swift @@ -31,7 +31,7 @@ struct SegmentedControl: View { } var body: some View { - HStack(spacing: 8) { + HStack(spacing: 4) { ForEach(options.indices, id: \.self) { index in SegmentedControlItem( label: options[index], diff --git a/CodeEdit/Features/Editor/Models/Editor.swift b/CodeEdit/Features/Editor/Models/Editor.swift index 2a494baa1f..ffc71f152c 100644 --- a/CodeEdit/Features/Editor/Models/Editor.swift +++ b/CodeEdit/Features/Editor/Models/Editor.swift @@ -40,11 +40,11 @@ final class Editor: ObservableObject, Identifiable { let tab = history[historyOffset] if !tabs.contains(tab) { - if let selectedTab { - openTab(file: tab.file, at: tabs.firstIndex(of: selectedTab), fromHistory: true) - } else { - openTab(file: tab.file, fromHistory: true) + if let temporaryTab, tabs.contains(temporaryTab) { + closeTab(file: temporaryTab.file, fromHistory: true) } + temporaryTab = tab + openTab(file: tab.file, fromHistory: true) } selectedTab = tab } @@ -64,29 +64,34 @@ final class Editor: ObservableObject, Identifiable { init() { self.tabs = [] + self.temporaryTab = nil self.parent = nil } init( files: OrderedSet = [], selectedTab: Tab? = nil, + temporaryTab: Tab? = nil, parent: SplitViewData? = nil ) { self.tabs = [] self.parent = parent files.forEach { openTab(file: $0) } self.selectedTab = selectedTab ?? (files.isEmpty ? nil : Tab(file: files.first!)) + self.temporaryTab = temporaryTab } init( files: OrderedSet = [], selectedTab: Tab? = nil, + temporaryTab: Tab? = nil, parent: SplitViewData? = nil ) { self.tabs = [] self.parent = parent files.forEach { openTab(file: $0.file) } self.selectedTab = selectedTab ?? tabs.first + self.temporaryTab = temporaryTab } /// Closes the editor. @@ -102,14 +107,15 @@ final class Editor: ObservableObject, Identifiable { /// Closes a tab in the editor. /// 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(file: CEWorkspaceFile) { + func closeTab(file: CEWorkspaceFile, fromHistory: Bool = false) { guard canCloseTab(file: file) else { return } if temporaryTab?.file == file { temporaryTab = nil } - - historyOffset = 0 + if !fromHistory { + historyOffset = 0 + } if file != selectedTab?.file { history.prepend(EditorInstance(file: file)) } @@ -149,7 +155,9 @@ final class Editor: ObservableObject, Identifiable { switch (temporaryTab, asTemporary) { case (.some(let tab), true): if let index = tabs.firstIndex(of: tab) { + history.removeFirst(historyOffset) history.prepend(item) + historyOffset = 0 tabs.remove(tab) tabs.insert(item, at: index) self.selectedTab = item diff --git a/CodeEdit/Features/Editor/Models/EditorLayout.swift b/CodeEdit/Features/Editor/Models/EditorLayout.swift index 86932561a7..ee803a76ab 100644 --- a/CodeEdit/Features/Editor/Models/EditorLayout.swift +++ b/CodeEdit/Features/Editor/Models/EditorLayout.swift @@ -89,6 +89,24 @@ enum EditorLayout: Equatable { } } + /// Gets flattened splitviews. + func getFlattened(parent: SplitViewData) -> [Editor] { + switch self { + case .one(let editor): + return [editor] + case .horizontal(let data), .vertical(let data): + if data.editorLayouts.count == 1 { + let one = data.editorLayouts[0] + if case .one(let editor) = one { + return [editor] + } + return [] + } else { + return data.getFlattened() + } + } + } + var isEmpty: Bool { switch self { case .one: diff --git a/CodeEdit/Features/Editor/Models/EditorManager.swift b/CodeEdit/Features/Editor/Models/EditorManager.swift index 3ce1866e20..4330d2e2b2 100644 --- a/CodeEdit/Features/Editor/Models/EditorManager.swift +++ b/CodeEdit/Features/Editor/Models/EditorManager.swift @@ -36,6 +36,17 @@ class EditorManager: ObservableObject { var tabBarTabIdSubject = PassthroughSubject() var cancellable: AnyCancellable? + // This caching mechanism is a temporary solution and is not optimized + @Published var updateCachedFlattenedEditors: Bool = true + var cachedFlettenedEditors: [Editor] = [] + var flattenedEditors: [Editor] { + if updateCachedFlattenedEditors { + cachedFlettenedEditors = self.getFlattened() + updateCachedFlattenedEditors = false + } + return cachedFlettenedEditors + } + // MARK: - Init init() { @@ -61,10 +72,21 @@ class EditorManager: ObservableObject { /// Flattens the splitviews. func flatten() { - if case .horizontal(let data) = editorLayout { - data.flatten() - } else if case .vertical(let data) = editorLayout { + switch editorLayout { + case .horizontal(let data), .vertical(let data): data.flatten() + default: + break + } + } + + /// Returns and array of flattened splitviews. + func getFlattened() -> [Editor] { + switch editorLayout { + case .horizontal(let data), .vertical(let data): + return data.getFlattened() + default: + return [] } } @@ -99,6 +121,7 @@ class EditorManager: ObservableObject { flatten() objectWillChange.send() + updateCachedFlattenedEditors = true } /// Set a new active editor. diff --git a/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarView.swift b/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarView.swift index 994de37599..3b488d4cc0 100644 --- a/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarView.swift +++ b/CodeEdit/Features/Editor/PathBar/Views/EditorPathBarView.swift @@ -9,6 +9,7 @@ import SwiftUI struct EditorPathBarView: View { private let file: CEWorkspaceFile? + private let shouldShowTabBar: Bool private let tappedOpenFile: (CEWorkspaceFile) -> Void @Environment(\.colorScheme) @@ -20,13 +21,15 @@ struct EditorPathBarView: View { @Environment(\.controlActiveState) private var activeState - static let height = 27.0 + static let height = 28.0 init( file: CEWorkspaceFile?, + shouldShowTabBar: Bool, tappedOpenFile: @escaping (CEWorkspaceFile) -> Void ) { self.file = file ?? nil + self.shouldShowTabBar = shouldShowTabBar self.tappedOpenFile = tappedOpenFile } @@ -63,7 +66,17 @@ struct EditorPathBarView: View { } } } - .padding(.horizontal, 10) + } + .padding(.horizontal, shouldShowTabBar ? (file == nil ? 10 : 4) : 0) + .safeAreaInset(edge: .leading, spacing: 0) { + if !shouldShowTabBar { + EditorTabBarLeadingAccessories() + } + } + .safeAreaInset(edge: .trailing, spacing: 0) { + if !shouldShowTabBar { + EditorTabBarTrailingAccessories() + } } .frame(height: Self.height, alignment: .center) .opacity(activeState == .inactive ? 0.8 : 1.0) diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarLeadingAccessories.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarLeadingAccessories.swift index 6879af8e87..d330da3e73 100644 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarLeadingAccessories.swift +++ b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarLeadingAccessories.swift @@ -74,11 +74,9 @@ struct EditorTabBarLeadingAccessories: View { } } label: { Image(systemName: "chevron.left") - .controlSize(.regular) - .opacity( - editor.historyOffset == editor.history.count-1 || editor.history.isEmpty - ? 0.5 : 1.0 - ) + .opacity(editor.historyOffset == editor.history.count-1 || editor.history.isEmpty ? 0.5 : 1) + .frame(height: EditorTabBarView.height - 2) + .padding(.horizontal, 4) } primaryAction: { editorManager.activeEditor = editor editor.goBackInHistory() @@ -103,8 +101,9 @@ struct EditorTabBarLeadingAccessories: View { } } label: { Image(systemName: "chevron.right") - .controlSize(.regular) - .opacity(editor.historyOffset == 0 ? 0.5 : 1.0) + .opacity(editor.historyOffset == 0 ? 0.5 : 1) + .frame(height: EditorTabBarView.height - 2) + .padding(.horizontal, 4) } primaryAction: { editorManager.activeEditor = editor editor.goForwardInHistory() @@ -112,11 +111,9 @@ struct EditorTabBarLeadingAccessories: View { .disabled(editor.historyOffset == 0) .help("Navigate forward") } + .buttonStyle(.icon) .controlSize(.small) .font(EditorTabBarAccessoryIcon.iconFont) - .frame(height: EditorTabBarView.height - 2) - .padding(.horizontal, 4) - .contentShape(Rectangle()) } .foregroundColor(.secondary) .buttonStyle(.plain) @@ -136,9 +133,3 @@ struct EditorTabBarLeadingAccessories: View { } } } - -struct TabBarLeadingAccessories_Previews: PreviewProvider { - static var previews: some View { - EditorTabBarLeadingAccessories() - } -} diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarTrailingAccessories.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarTrailingAccessories.swift index 7b8b685078..f023729ca1 100644 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarTrailingAccessories.swift +++ b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarTrailingAccessories.swift @@ -69,11 +69,12 @@ struct EditorTabBarTrailingAccessories: View { func split(edge: Edge) { let newEditor: Editor if let tab = editor.selectedTab { - newEditor = .init(files: [tab]) + newEditor = .init(files: [tab], temporaryTab: tab) } else { newEditor = .init() } splitEditor(edge, newEditor) + editorManager.updateCachedFlattenedEditors = true editorManager.activeEditor = newEditor } } diff --git a/CodeEdit/Features/Editor/Views/EditorView.swift b/CodeEdit/Features/Editor/Views/EditorView.swift index 4a9a16781e..6ca74ae35c 100644 --- a/CodeEdit/Features/Editor/Views/EditorView.swift +++ b/CodeEdit/Features/Editor/Views/EditorView.swift @@ -11,6 +11,9 @@ struct EditorView: View { @AppSettings(\.general.showEditorPathBar) var showEditorPathBar + @AppSettings(\.navigation.navigationStyle) + var navigationStyle + @AppSettings(\.general.dimEditorsWithoutFocus) var dimEditorsWithoutFocus @@ -20,24 +23,32 @@ struct EditorView: View { @EnvironmentObject private var editorManager: EditorManager - var editorInsetAmount: Double { - let tabBarHeight = EditorTabBarView.height + 1 - let pathBarHeight = showEditorPathBar ? (EditorPathBarView.height + 1) : 0 - return tabBarHeight + pathBarHeight - } - var body: some View { + var shouldShowTabBar: Bool { + return navigationStyle == .openInTabs + || editorManager.flattenedEditors.contains { editor in + (editor.temporaryTab == nil && !editor.tabs.isEmpty) + || (editor.temporaryTab != nil && editor.tabs.count > 1) + } + } + + var editorInsetAmount: Double { + let tabBarHeight = shouldShowTabBar ? (EditorTabBarView.height + 1) : 0 + let pathBarHeight = showEditorPathBar ? (EditorPathBarView.height + 1) : 0 + return tabBarHeight + pathBarHeight + } + VStack { if let selected = editor.selectedTab { WorkspaceCodeFileView( file: selected.file, textViewCoordinators: [selected.rangeTranslator].compactMap({ $0 }) ) - .focusedObject(editor) - .transformEnvironment(\.edgeInsets) { insets in - insets.top += editorInsetAmount - } - .opacity(dimEditorsWithoutFocus && editor != editorManager.activeEditor ? 0.5 : 1) + .focusedObject(editor) + .transformEnvironment(\.edgeInsets) { insets in + insets.top += editorInsetAmount + } + .opacity(dimEditorsWithoutFocus && editor != editorManager.activeEditor ? 0.5 : 1) } else { CEContentUnavailableView("No Editor") .padding(.top, editorInsetAmount) @@ -50,16 +61,23 @@ struct EditorView: View { .ignoresSafeArea(.all) .safeAreaInset(edge: .top, spacing: 0) { VStack(spacing: 0) { - EditorTabBarView() - .id("TabBarView" + editor.id.uuidString) - .environmentObject(editor) - Divider() + if shouldShowTabBar { + EditorTabBarView() + .id("TabBarView" + editor.id.uuidString) + .environmentObject(editor) + Divider() + } if showEditorPathBar { - EditorPathBarView(file: editor.selectedTab?.file) { [weak editor] newFile in + EditorPathBarView( + file: editor.selectedTab?.file, + shouldShowTabBar: shouldShowTabBar + ) { [weak editor] newFile in if let file = editor?.selectedTab, let index = editor?.tabs.firstIndex(of: file) { editor?.openTab(file: newFile, at: index) } } + .environmentObject(editor) + .padding(.top, shouldShowTabBar ? -1 : 0) Divider() } } @@ -68,7 +86,14 @@ struct EditorView: View { } .focused($focus, equals: editor) .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CodeEditor.didBeginEditing"))) { _ in - editor.temporaryTab = nil + if navigationStyle == .openInTabs { + editor.temporaryTab = nil + } + } + .onChange(of: navigationStyle) { newValue in + if newValue == .openInPlace && editor.tabs.count == 1 { + editor.temporaryTab = editor.tabs[0] + } } } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift index 12ddae603a..ef12c300c6 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift @@ -29,6 +29,7 @@ final class ProjectNavigatorViewController: NSViewController { var workspace: WorkspaceDocument? var iconColor: SettingsData.FileIconStyle = .color + var fileExtensionsVisibility: SettingsData.FileExtensionsVisibility = .showAll var shownFileExtensions: SettingsData.FileExtensions = .default var hiddenFileExtensions: SettingsData.FileExtensions = .default @@ -119,7 +120,7 @@ final class ProjectNavigatorViewController: NSViewController { } else { outlineView.expandItem(item) } - } else { + } else if Settings[\.navigation].navigationStyle == .openInTabs { workspace?.editorManager.activeEditor.openTab(file: item, asTemporary: false) } } diff --git a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift index ccdf86216a..c3f534d9be 100644 --- a/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift +++ b/CodeEdit/Features/NavigatorArea/SourceControlNavigator/Views/SourceControlNavigatorView.swift @@ -46,7 +46,7 @@ struct SourceControlNavigatorTabs: View { prominent: true ) .frame(maxWidth: .infinity) - .frame(height: 26) + .frame(height: 27) .padding(.horizontal, 8) .task { do { diff --git a/CodeEdit/Features/Settings/Models/SettingsData.swift b/CodeEdit/Features/Settings/Models/SettingsData.swift index 05c48c719d..2eebf5af0b 100644 --- a/CodeEdit/Features/Settings/Models/SettingsData.swift +++ b/CodeEdit/Features/Settings/Models/SettingsData.swift @@ -29,6 +29,9 @@ struct SettingsData: Codable, Hashable { /// The global settings for accounts var accounts: AccountsSettings = .init() + /// The global settings for themes + var navigation: NavigationSettings = .init() + /// The global settings for themes var theme: ThemeSettings = .init() @@ -58,6 +61,7 @@ struct SettingsData: Codable, Hashable { let container = try decoder.container(keyedBy: CodingKeys.self) self.general = try container.decodeIfPresent(GeneralSettings.self, forKey: .general) ?? .init() self.accounts = try container.decodeIfPresent(AccountsSettings.self, forKey: .accounts) ?? .init() + self.navigation = try container.decodeIfPresent(NavigationSettings.self, forKey: .navigation) ?? .init() self.theme = try container.decodeIfPresent(ThemeSettings.self, forKey: .theme) ?? .init() self.terminal = try container.decodeIfPresent(TerminalSettings.self, forKey: .terminal) ?? .init() self.textEditing = try container.decodeIfPresent(TextEditingSettings.self, forKey: .textEditing) ?? .init() @@ -82,6 +86,8 @@ struct SettingsData: Codable, Hashable { general.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } case .accounts: accounts.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } + case .navigation: + navigation.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } case .theme: theme.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } case .textEditing: @@ -97,7 +103,6 @@ struct SettingsData: Codable, Hashable { case .featureFlags: featureFlags.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) } case .behavior: return [.init(name, settingName: "Error")] - case .navigation: return [.init(name, settingName: "Error")] case .components: return [.init(name, settingName: "Error")] case .keybindings: return [.init(name, settingName: "Error")] case .advanced: return [.init(name, settingName: "Error")] diff --git a/CodeEdit/Features/Settings/Pages/NavigationSettings/Models/NavigationSettings.swift b/CodeEdit/Features/Settings/Pages/NavigationSettings/Models/NavigationSettings.swift new file mode 100644 index 0000000000..fa95f98d37 --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/NavigationSettings/Models/NavigationSettings.swift @@ -0,0 +1,42 @@ +// +// NavigationSettings.swift +// CodeEdit +// +// Created by Austin Condiff on 3/4/24. +// + +import Foundation + +extension SettingsData { + + /// The global settings for the terminal emulator + struct NavigationSettings: Codable, Hashable, SearchableSettingsPage { + + /// The search keys + var searchKeys: [String] { + [ + "Navigation Style", + ] + .map { NSLocalizedString($0, comment: "") } + } + + /// Navigation style used + var navigationStyle: NavigationStyle = .openInTabs + + /// Default initializer + init() {} + + /// Explicit decoder init for setting default values when key is not present in `JSON` + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.navigationStyle = try container.decodeIfPresent( + NavigationStyle.self, forKey: .navigationStyle + ) ?? .openInTabs + } + } + + enum NavigationStyle: String, Codable, Hashable { + case openInTabs + case openInPlace + } +} diff --git a/CodeEdit/Features/Settings/Pages/NavigationSettings/NavigationSettingsView.swift b/CodeEdit/Features/Settings/Pages/NavigationSettings/NavigationSettingsView.swift new file mode 100644 index 0000000000..552eb4a075 --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/NavigationSettings/NavigationSettingsView.swift @@ -0,0 +1,32 @@ +// +// NavigationSettingsView.swift +// CodeEdit +// +// Created by Austin Condiff on 3/4/24. +// + +import SwiftUI + +struct NavigationSettingsView: View { + @AppSettings(\.navigation) + var settings + + var body: some View { + SettingsForm { + Section { + navigationStyle + } + } + } +} + +private extension NavigationSettingsView { + private var navigationStyle: some View { + Picker("Navigation Style", selection: $settings.navigationStyle) { + Text("Open in Tabs") + .tag(SettingsData.NavigationStyle.openInTabs) + Text("Open in Place") + .tag(SettingsData.NavigationStyle.openInPlace) + } + } +} diff --git a/CodeEdit/Features/Settings/SettingsView.swift b/CodeEdit/Features/Settings/SettingsView.swift index faddda296f..a40d76dba9 100644 --- a/CodeEdit/Features/Settings/SettingsView.swift +++ b/CodeEdit/Features/Settings/SettingsView.swift @@ -36,6 +36,13 @@ struct SettingsView: View { icon: .system("at") ) ), + .init( + SettingsPage( + .navigation, + baseColor: .green, + icon: .system("arrow.triangle.turn.up.right.diamond.fill") + ) + ), .init( SettingsPage( .theme, @@ -152,6 +159,8 @@ struct SettingsView: View { GeneralSettingsView().environmentObject(updater) case .accounts: AccountsSettingsView() + case .navigation: + NavigationSettingsView() case .theme: ThemeSettingsView() case .textEditing: diff --git a/CodeEdit/Features/SplitView/Model/SplitViewData.swift b/CodeEdit/Features/SplitView/Model/SplitViewData.swift index 5eb8275edb..a874085764 100644 --- a/CodeEdit/Features/SplitView/Model/SplitViewData.swift +++ b/CodeEdit/Features/SplitView/Model/SplitViewData.swift @@ -85,4 +85,13 @@ final class SplitViewData: ObservableObject { editorLayouts[index].flatten(parent: self) } } + + /// Gets flattened splitviews. + func getFlattened() -> [Editor] { + var arr: [Editor] = [] + for index in editorLayouts.indices { + arr += editorLayouts[index].getFlattened(parent: self) + } + return arr + } }