diff --git a/CodeEdit/Features/Editor/Models/Editor+History.swift b/CodeEdit/Features/Editor/Models/Editor+History.swift index 79412cf86..762711f9a 100644 --- a/CodeEdit/Features/Editor/Models/Editor+History.swift +++ b/CodeEdit/Features/Editor/Models/Editor+History.swift @@ -64,7 +64,7 @@ extension Editor { closeTab(file: temporaryTab.file, fromHistory: true) } temporaryTab = tab - openTab(tab: tab, fromHistory: true) + openTab(file: tab.file, fromHistory: true) } selectedTab = tab } diff --git a/CodeEdit/Features/Editor/Models/Editor.swift b/CodeEdit/Features/Editor/Models/Editor.swift index 25f5e3359..f22648a6c 100644 --- a/CodeEdit/Features/Editor/Models/Editor.swift +++ b/CodeEdit/Features/Editor/Models/Editor.swift @@ -70,7 +70,7 @@ final class Editor: ObservableObject, Identifiable { ) { self.tabs = [] self.parent = parent - files.forEach { openTab(tab: Tab(file: $0)) } + files.forEach { openTab(file: $0) } self.selectedTab = selectedTab ?? (files.isEmpty ? nil : Tab(file: files.first!)) self.temporaryTab = temporaryTab } @@ -83,7 +83,7 @@ final class Editor: ObservableObject, Identifiable { ) { self.tabs = [] self.parent = parent - files.forEach { openTab(tab: $0) } + files.forEach { openTab(file: $0.file) } self.selectedTab = selectedTab ?? tabs.first self.temporaryTab = temporaryTab } @@ -145,11 +145,11 @@ final class Editor: ObservableObject, Identifiable { /// - file: the file to open. /// - asTemporary: indicates whether the tab should be opened as a temporary tab or a permanent tab. func openTab(file: CEWorkspaceFile, asTemporary: Bool) { - let newTabItem = Tab(file: file) + let item = EditorInstance(file: file) // Item is already opened in a tab. - guard !tabs.contains(newTabItem) || !asTemporary else { - selectedTab = newTabItem - addToHistory(newTabItem) + guard !tabs.contains(item) || !asTemporary else { + selectedTab = item + addToHistory(item) return } @@ -157,27 +157,22 @@ final class Editor: ObservableObject, Identifiable { case (.some(let tab), true): if let index = tabs.firstIndex(of: tab) { clearFuture() - addToHistory(newTabItem) + addToHistory(item) tabs.remove(tab) - tabs.insert(newTabItem, at: index) - self.selectedTab = newTabItem - temporaryTab = newTabItem - do { - try openFile(item: newTabItem) - } catch { - print(error) - } + tabs.insert(item, at: index) + self.selectedTab = item + temporaryTab = item } - case (.some(let tab), false) where tab.file == newTabItem.file: + case (.some(let tab), false) where tab == item: temporaryTab = nil case (.none, true): - openTab(tab: newTabItem) - temporaryTab = newTabItem + openTab(file: item.file) + temporaryTab = item case (.none, false): - openTab(tab: newTabItem) + openTab(file: item.file) default: break @@ -189,24 +184,25 @@ final class Editor: ObservableObject, Identifiable { /// - file: 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(tab: Tab, at index: Int? = nil, fromHistory: Bool = false) { + func openTab(file: CEWorkspaceFile, at index: Int? = nil, fromHistory: Bool = false) { + let item = Tab(file: file) if let index { - tabs.insert(tab, at: index) + tabs.insert(item, at: index) } else { if let selectedTab, let currentIndex = tabs.firstIndex(of: selectedTab) { - tabs.insert(tab, at: tabs.index(after: currentIndex)) + tabs.insert(item, at: tabs.index(after: currentIndex)) } else { - tabs.append(tab) + tabs.append(item) } } - selectedTab = tab + selectedTab = item if !fromHistory { clearFuture() - addToHistory(tab) + addToHistory(item) } do { - try openFile(item: tab) + try openFile(item: item) } catch { print(error) } diff --git a/CodeEdit/Features/Editor/Models/EditorInstance.swift b/CodeEdit/Features/Editor/Models/EditorInstance.swift index ae71cf062..f8aeb8ebc 100644 --- a/CodeEdit/Features/Editor/Models/EditorInstance.swift +++ b/CodeEdit/Features/Editor/Models/EditorInstance.swift @@ -13,34 +13,39 @@ import CodeEditSourceEditor /// A single instance of an editor in a group with a published ``EditorInstance/cursorPositions`` variable to publish /// the user's current location in a file. -/// -/// Use this object instead of a `CEWorkspaceFile` or `CodeFileDocument` when something related to *one* editor needs -/// to happen. For instance, storing the current cursor positions for a single editor. -class EditorInstance: Hashable, ObservableObject { +class EditorInstance: Hashable { + // Public - /// The file presented in this editor instance. This is not unique. + /// The file presented in this editor instance. let file: CEWorkspaceFile - @Published var cursorPositions: [CursorPosition] + /// A publisher for the user's current location in a file. + var cursorPositions: AnyPublisher<[CursorPosition], Never> { + cursorSubject.eraseToAnyPublisher() + } + + // Public TextViewCoordinator APIs + + var rangeTranslator: RangeTranslator? - lazy var rangeTranslator: RangeTranslator = { - RangeTranslator(parent: self) - }() + // Internal Combine subjects + + private let cursorSubject = CurrentValueSubject<[CursorPosition], Never>([]) // MARK: - Init, Hashable, Equatable init(file: CEWorkspaceFile, cursorPositions: [CursorPosition] = []) { self.file = file - self.cursorPositions = cursorPositions + self.cursorSubject.send(cursorPositions) + self.rangeTranslator = RangeTranslator(cursorSubject: cursorSubject) } func hash(into hasher: inout Hasher) { hasher.combine(file) - hasher.combine(cursorPositions) } static func == (lhs: EditorInstance, rhs: EditorInstance) -> Bool { - lhs.file == rhs.file && lhs.cursorPositions == rhs.cursorPositions + lhs.file == rhs.file } // MARK: - RangeTranslator @@ -48,19 +53,19 @@ class EditorInstance: Hashable, ObservableObject { /// Translates ranges (eg: from a cursor position) to other information like the number of lines in a range. class RangeTranslator: TextViewCoordinator { private weak var textViewController: TextViewController? - private weak var editorInstance: EditorInstance? + private var cursorSubject: CurrentValueSubject<[CursorPosition], Never> - fileprivate init(parent: EditorInstance) { - self.editorInstance = parent + init(cursorSubject: CurrentValueSubject<[CursorPosition], Never>) { + self.cursorSubject = cursorSubject } - func prepareCoordinator(controller: TextViewController) { - self.textViewController = controller - self.editorInstance?.cursorPositions = controller.cursorPositions + func textViewDidChangeSelection(controller: TextViewController, newPositions: [CursorPosition]) { + self.cursorSubject.send(controller.cursorPositions) } - func textViewDidChangeSelection(controller: TextViewController, newPositions: [CursorPosition]) { - self.editorInstance?.cursorPositions = newPositions + func prepareCoordinator(controller: TextViewController) { + self.textViewController = controller + self.cursorSubject.send(controller.cursorPositions) } func destroy() { diff --git a/CodeEdit/Features/Editor/Views/EditorAreaView.swift b/CodeEdit/Features/Editor/Views/EditorAreaView.swift index 3bd6704af..bc88ad20a 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaView.swift @@ -62,6 +62,7 @@ struct EditorAreaView: View { } else { LoadingFileView(selected.file.name) } + } else { CEContentUnavailableView("No Editor") .padding(.top, editorInsetAmount) @@ -86,7 +87,7 @@ struct EditorAreaView: View { shouldShowTabBar: shouldShowTabBar ) { [weak editor] newFile in if let file = editor?.selectedTab, let index = editor?.tabs.firstIndex(of: file) { - editor?.openTab(tab: EditorInstance(file: newFile), at: index) + editor?.openTab(file: newFile, at: index) } } .environmentObject(editor) diff --git a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorPositionLabel.swift b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorPositionLabel.swift index bdead53a5..a47e85176 100644 --- a/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorPositionLabel.swift +++ b/CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorPositionLabel.swift @@ -61,7 +61,7 @@ struct StatusBarCursorPositionLabel: View { .font(statusBarViewModel.statusBarFont) .foregroundColor(foregroundColor) .lineLimit(1) - .onReceive(editorInstance.$cursorPositions) { newValue in + .onReceive(editorInstance.cursorPositions) { newValue in self.cursorPositions = newValue } } @@ -78,7 +78,7 @@ struct StatusBarCursorPositionLabel: View { /// - Parameter range: The range to query. /// - Returns: The number of lines in the range. func getLines(_ range: NSRange) -> Int { - return editorInstance.rangeTranslator.linesInRange(range) + return editorInstance.rangeTranslator?.linesInRange(range) ?? 0 } /// Create a label string for cursor positions.