Skip to content

Fix Navigator Key Navigation Explosion #1803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
6C5B63DE29C76213005454BA /* WindowCodeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5B63DD29C76213005454BA /* WindowCodeFileView.swift */; };
6C5C891B2A3F736500A94FE1 /* FocusedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5C891A2A3F736500A94FE1 /* FocusedValues.swift */; };
6C5FDF7A29E6160000BC08C0 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5FDF7929E6160000BC08C0 /* AppSettings.swift */; };
6C6362D42C3E321A0025570D /* Editor+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6362D32C3E321A0025570D /* Editor+History.swift */; };
6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 6C66C31229D05CDC00DE9ED2 /* GRDB */; };
6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */; };
6C6BD6F129CD13FA00235D17 /* ExtensionDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */; };
Expand All @@ -402,6 +403,7 @@
6C85BB402C2105ED00EB5DEF /* CodeEditKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C85BB3F2C2105ED00EB5DEF /* CodeEditKit */; };
6C85BB412C21061A00EB5DEF /* GitHubComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3C29301D8F00AC7927 /* GitHubComment.swift */; };
6C85BB442C210EFD00EB5DEF /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 6C85BB432C210EFD00EB5DEF /* SwiftUIIntrospect */; };
6C85F7562C3CA638008E9836 /* EditorHistoryMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C85F7552C3CA638008E9836 /* EditorHistoryMenus.swift */; };
6C91D57229B176FF0059A90D /* EditorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C91D57129B176FF0059A90D /* EditorManager.swift */; };
6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */; };
6CA1AE952B46950000378EAB /* EditorInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CA1AE942B46950000378EAB /* EditorInstance.swift */; };
Expand Down Expand Up @@ -997,6 +999,7 @@
6C5B63DD29C76213005454BA /* WindowCodeFileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowCodeFileView.swift; sourceTree = "<group>"; };
6C5C891A2A3F736500A94FE1 /* FocusedValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedValues.swift; sourceTree = "<group>"; };
6C5FDF7929E6160000BC08C0 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
6C6362D32C3E321A0025570D /* Editor+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Editor+History.swift"; sourceTree = "<group>"; };
6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionManagerWindow.swift; sourceTree = "<group>"; };
6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDiscovery.swift; sourceTree = "<group>"; };
6C6BD6F529CD145F00235D17 /* ExtensionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInfo.swift; sourceTree = "<group>"; };
Expand All @@ -1010,6 +1013,7 @@
6C82D6B829BFE34900495C54 /* HelpCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpCommands.swift; sourceTree = "<group>"; };
6C82D6BB29C00CD900495C54 /* FirstResponderPropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstResponderPropertyWrapper.swift; sourceTree = "<group>"; };
6C82D6C529C012AD00495C54 /* NSApp+openWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSApp+openWindow.swift"; sourceTree = "<group>"; };
6C85F7552C3CA638008E9836 /* EditorHistoryMenus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorHistoryMenus.swift; sourceTree = "<group>"; };
6C91D57129B176FF0059A90D /* EditorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorManager.swift; sourceTree = "<group>"; };
6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = "<group>"; };
6CA1AE942B46950000378EAB /* EditorInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorInstance.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2308,6 +2312,7 @@
DE6F77862813625500D00A76 /* EditorTabBarDivider.swift */,
287776E827E34BC700D46668 /* EditorTabBarView.swift */,
B6AB09A22AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift */,
6C85F7552C3CA638008E9836 /* EditorHistoryMenus.swift */,
B6AB09A42AAAC00F0003A3A6 /* EditorTabBarTrailingAccessories.swift */,
);
path = Views;
Expand Down Expand Up @@ -2973,6 +2978,7 @@
isa = PBXGroup;
children = (
6C147C3D29A3281D0089B630 /* Editor.swift */,
6C6362D32C3E321A0025570D /* Editor+History.swift */,
5994B6D92BD6B408006A4C5F /* Editor+TabSwitch.swift */,
6CA1AE942B46950000378EAB /* EditorInstance.swift */,
6C147C3E29A3281D0089B630 /* EditorLayout.swift */,
Expand Down Expand Up @@ -3941,6 +3947,7 @@
30B088162C0D53080063A882 /* LSPCache.swift in Sources */,
B6F0517929D9E3C900D72287 /* SourceControlGitView.swift in Sources */,
587B9E8329301D8F00AC7927 /* GitHubPullRequest.swift in Sources */,
6C85F7562C3CA638008E9836 /* EditorHistoryMenus.swift in Sources */,
5878DA82291863F900DD95A3 /* AcknowledgementsView.swift in Sources */,
587B9E8529301D8F00AC7927 /* GitHubReview.swift in Sources */,
58D01C9A293167DC00C5B6B4 /* CodeEditKeychain.swift in Sources */,
Expand Down Expand Up @@ -3975,6 +3982,7 @@
30B087FC2C0D53080063A882 /* LanguageServer+CallHierarchy.swift in Sources */,
6CFF967C29BEBD5200182D6F /* WindowCommands.swift in Sources */,
587B9E7229301D8F00AC7927 /* GitJSONPostRouter.swift in Sources */,
6C6362D42C3E321A0025570D /* Editor+History.swift in Sources */,
6C85BB412C21061A00EB5DEF /* GitHubComment.swift in Sources */,
5878DAB0291D627C00DD95A3 /* EditorPathBarMenu.swift in Sources */,
04BA7C242AE2E7CD00584E1C /* SourceControlNavigatorSyncView.swift in Sources */,
Expand Down
71 changes: 71 additions & 0 deletions CodeEdit/Features/Editor/Models/Editor+History.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// Editor+History.swift
// CodeEdit
//
// Created by Khan Winter on 7/9/24.
//

import Foundation

/// Methods for modifying the history list on the editor.
extension Editor {
/// Add the tab to the history list.
/// - Parameter tab: The tab to add to the history.
func addToHistory(_ tab: Tab) {
if history.first != tab {
history.prepend(tab)
}
}

/// Clear any tabs in the "future" on the history list. Resets the history offset and removes any tabs that were
/// available to navigate forwards to.
func clearFuture() {
guard historyOffset > 0 else { return } // nothing to clear, avoid an out of bounds error
history.removeFirst(historyOffset)
historyOffset = 0
}

/// Move backwards in the history list by one place.
func goBackInHistory() {
if canGoBackInHistory {
historyOffset += 1
}
}

/// Move forwards in the history list by one place.
func goForwardInHistory() {
if canGoForwardInHistory {
historyOffset -= 1
}
}

// TODO: move to @Observable so this works better
/// Warning: NOT published!
var canGoBackInHistory: Bool {
historyOffset != history.count - 1 && !history.isEmpty
}

// TODO: move to @Observable so this works better
/// Warning: NOT published!
var canGoForwardInHistory: Bool {
historyOffset != 0
}

/// Called by the ``Editor`` class when the history offset is changed.
///
/// This method updates the selected tab to the current tab in the history offset.
/// If the tab is not opened, it is opened without modifying the history list.
/// - Warning: Do not use except in the ``historyOffset``'s `didSet`.
func historyOffsetDidChange() {
let tab = history[historyOffset]

if !tabs.contains(tab) {
if let temporaryTab, tabs.contains(temporaryTab) {
closeTab(file: temporaryTab.file, fromHistory: true)
}
temporaryTab = tab
openTab(file: tab.file, fromHistory: true)
}
selectedTab = tab
}
}
65 changes: 19 additions & 46 deletions CodeEdit/Features/Editor/Models/Editor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,16 @@ final class Editor: ObservableObject, Identifiable {
}

/// The current offset in the history list.
/// When set, updates the ``selectedTab`` to the tab indicated by the offset.
/// See the ``historyOffsetDidChange()`` method for more details.
@Published var historyOffset: Int = 0 {
didSet {
let tab = history[historyOffset]

if !tabs.contains(tab) {
if let temporaryTab, tabs.contains(temporaryTab) {
closeTab(file: temporaryTab.file, fromHistory: true)
}
temporaryTab = tab
openTab(file: tab.file, fromHistory: true)
}
selectedTab = tab
historyOffsetDidChange()
}
}

/// History of tab switching.
/// Maintains the list of tabs that have been switched to.
/// - Warning: Use the ``addToHistory(_:)`` or ``clearFuture()`` methods to modify this. Do not modify directly.
@Published var history: Deque<Tab> = []

/// Currently selected tab.
Expand Down Expand Up @@ -106,22 +100,26 @@ 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.
/// - Parameters:
/// - file: The tab to close
/// - fromHistory: If `true`, does not clear tabs ahead of the ``historyOffset``
/// Used when opening tabs from the history queue where tabs ahead of the ``historyOffset`` should
/// not be removed.
func closeTab(file: CEWorkspaceFile, fromHistory: Bool = false) {
guard canCloseTab(file: file) else { return }

if temporaryTab?.file == file {
temporaryTab = nil
}
if !fromHistory {
historyOffset = 0
clearFuture()
}
if file != selectedTab?.file {
history.prepend(EditorInstance(file: file))
addToHistory(EditorInstance(file: file))
}
removeTab(file)
if let selectedTab {
history.prepend(selectedTab)
addToHistory(selectedTab)
}
// Reset change count to 0
file.fileDocument?.updateChangeCount(.changeCleared)
Expand All @@ -148,16 +146,15 @@ final class Editor: ObservableObject, Identifiable {
// Item is already opened in a tab.
guard !tabs.contains(item) || !asTemporary else {
selectedTab = item
history.prepend(item)
addToHistory(item)
return
}

switch (temporaryTab, asTemporary) {
case (.some(let tab), true):
if let index = tabs.firstIndex(of: tab) {
history.removeFirst(historyOffset)
history.prepend(item)
historyOffset = 0
clearFuture()
addToHistory(item)
tabs.remove(tab)
tabs.insert(item, at: index)
self.selectedTab = item
Expand Down Expand Up @@ -198,9 +195,8 @@ final class Editor: ObservableObject, Identifiable {

selectedTab = item
if !fromHistory {
history.removeFirst(historyOffset)
history.prepend(item)
historyOffset = 0
clearFuture()
addToHistory(item)
}
do {
try openFile(item: item)
Expand All @@ -225,31 +221,8 @@ final class Editor: ObservableObject, Identifiable {
CodeEditDocumentController.shared.addDocument(codeFile)
}

func goBackInHistory() {
if canGoBackInHistory {
historyOffset += 1
}
}

func goForwardInHistory() {
if canGoForwardInHistory {
historyOffset -= 1
}
}

// TODO: move to @Observable so this works better
/// Warning: NOT published!
var canGoBackInHistory: Bool {
historyOffset != history.count-1 && !history.isEmpty
}

// TODO: move to @Observable so this works better
/// Warning: NOT published!
var canGoForwardInHistory: Bool {
historyOffset != 0
}

/// Check if tab can be closed
///
/// If document edited it will show dialog where user can save document before closing or cancel.
private func canCloseTab(file: CEWorkspaceFile) -> Bool {
guard let codeFile = file.fileDocument else { return true }
Expand Down
5 changes: 2 additions & 3 deletions CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,8 @@ struct EditorTabView: View {
if editor.selectedTab?.file != item {
let tabItem = EditorInstance(file: item)
editor.selectedTab = tabItem
editor.history.removeFirst(editor.historyOffset)
editor.history.prepend(tabItem)
editor.historyOffset = 0
editor.clearFuture()
editor.addToHistory(tabItem)
}
}

Expand Down
78 changes: 78 additions & 0 deletions CodeEdit/Features/Editor/TabBar/Views/EditorHistoryMenus.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// EditorHistoryMenus.swift
// CodeEdit
//
// Created by Khan Winter on 7/8/24.
//

import SwiftUI

struct EditorHistoryMenus: View {
@EnvironmentObject private var editorManager: EditorManager
@EnvironmentObject private var editor: Editor

var body: some View {
Group {
Menu {
ForEach(
Array(editor.history.dropFirst(editor.historyOffset+1).enumerated()),
id: \.offset
) { index, tab in
Button {
editorManager.activeEditor = editor
editor.historyOffset += index + 1
} label: {
HStack {
tab.file.icon
Text(tab.file.name)
}
}
}
} label: {
Image(systemName: "chevron.left")
.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()
}
.disabled(editor.historyOffset == editor.history.count - 1 || editor.history.isEmpty)
.help("Navigate back")

Menu {
ForEach(
Array(editor.history.prefix(editor.historyOffset).reversed().enumerated()),
id: \.offset
) { index, tab in
Button {
editorManager.activeEditor = editor
editor.historyOffset -= index + 1
} label: {
HStack {
tab.file.icon
Text(tab.file.name)
}
}
}
} label: {
Image(systemName: "chevron.right")
.opacity(editor.historyOffset == 0 ? 0.5 : 1)
.frame(height: EditorTabBarView.height - 2)
.padding(.horizontal, 4)
} primaryAction: {
editorManager.activeEditor = editor
editor.goForwardInHistory()
}
.disabled(editor.historyOffset == 0)
.help("Navigate forward")
}
.buttonStyle(.icon)
.controlSize(.small)
.font(EditorTabBarAccessoryIcon.iconFont)
}
}

#Preview {
EditorHistoryMenus()
}
Loading
Loading