From 4d9a5d44f377463fe1fdda7543cfaac08c70389d 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 df34db92c..b0cbe368a 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 847168345..040dc189f 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 05b97e270..0b142e54e 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 2a494baa1..ffc71f152 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 86932561a..ee803a76a 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 3ce1866e2..4330d2e2b 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 994de3759..3b488d4cc 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 6879af8e8..d330da3e7 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 7b8b68507..f023729ca 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 4a9a16781..6ca74ae35 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 12ddae603..ef12c300c 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 ccdf86216..c3f534d9b 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 05c48c719..2eebf5af0 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 000000000..fa95f98d3 --- /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 000000000..552eb4a07 --- /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 faddda296..a40d76dba 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 5eb8275ed..a87408576 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 + } }