Skip to content

Commit

Permalink
Fix Navigator Key Navigation Explosion (#1803)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecoolwinter authored Jul 14, 2024
1 parent f3524cc commit 451d4c2
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 116 deletions.
8 changes: 8 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,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 @@ -404,6 +405,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 */; };
6C9619202C3F27E3009733CE /* ProjectNavigatorUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C96191B2C3F27E3009733CE /* ProjectNavigatorUITests.swift */; };
6C9619222C3F27F1009733CE /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C9619212C3F27F1009733CE /* Query.swift */; };
Expand Down Expand Up @@ -1004,6 +1006,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 @@ -1017,6 +1020,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>"; };
6C96191B2C3F27E3009733CE /* ProjectNavigatorUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorUITests.swift; sourceTree = "<group>"; };
6C9619212C3F27F1009733CE /* Query.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2319,6 +2323,7 @@
DE6F77862813625500D00A76 /* EditorTabBarDivider.swift */,
287776E827E34BC700D46668 /* EditorTabBarView.swift */,
B6AB09A22AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift */,
6C85F7552C3CA638008E9836 /* EditorHistoryMenus.swift */,
B6AB09A42AAAC00F0003A3A6 /* EditorTabBarTrailingAccessories.swift */,
);
path = Views;
Expand Down Expand Up @@ -3030,6 +3035,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 @@ -3998,6 +4004,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 @@ -4033,6 +4040,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

0 comments on commit 451d4c2

Please sign in to comment.