diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9975f9a27..b1f1976b2 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -39,7 +39,7 @@ { "identity" : "codeedittextview", "kind" : "remoteSourceControl", - "location" : "https://github.com/CodeEditApp/CodeEditTextView.git", + "location" : "https://github.com/CodeEditApp/CodeEditTextView", "state" : { "revision" : "7f130bd50bb9eb6bacf6a42700cce571ec82bd64", "version" : "0.6.7" @@ -239,8 +239,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Wouter01/SwiftUI-WindowManagement", "state" : { - "revision" : "03642ad06a3aa51e8284eb22146a208269cdc1ca", - "version" : "2.1.0" + "revision" : "adbebf5d7df325f3d7bf07dc832e5e162a9003f5", + "version" : "2.1.1" } }, { diff --git a/CodeEdit/Features/CodeFile/CodeFile.swift b/CodeEdit/Features/CodeFile/CodeFile.swift index 0c37a176d..a60369e6b 100644 --- a/CodeEdit/Features/CodeFile/CodeFile.swift +++ b/CodeEdit/Features/CodeFile/CodeFile.swift @@ -71,6 +71,8 @@ final class CodeFileDocument: NSDocument, ObservableObject, QLPreviewItem { @Published var cursorPosition = (1, 1) + @Published var isDirty: Bool = false + // MARK: - NSDocument override class var autosavesInPlace: Bool { diff --git a/CodeEdit/Features/CodeFile/CodeFileView.swift b/CodeEdit/Features/CodeFile/CodeFileView.swift index 3c3ecc3b5..5482d8ba7 100644 --- a/CodeEdit/Features/CodeFile/CodeFileView.swift +++ b/CodeEdit/Features/CodeFile/CodeFileView.swift @@ -37,6 +37,8 @@ struct CodeFileView: View { @Environment(\.colorScheme) private var colorScheme + @EnvironmentObject private var editorManager: EditorManager + @StateObject private var themeModel: ThemeModel = .shared private var cancellables = [AnyCancellable]() @@ -49,6 +51,14 @@ struct CodeFileView: View { self.codeFile = codeFile self.isEditable = isEditable + codeFile + .$content + .dropFirst() + .sink { _ in + codeFile.isDirty = true + } + .store(in: &cancellables) + codeFile .$content .dropFirst() diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index 23a33ecaa..995758b44 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -240,7 +240,9 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs } @IBAction func saveDocument(_ sender: Any) { - getSelectedCodeFile()?.save(sender) + guard let codeFile = getSelectedCodeFile() else { return } + codeFile.save(sender) + codeFile.isDirty = false workspace?.editorManager.activeEditor.temporaryTab = nil } diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift index e84e0559e..2c0552d34 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift @@ -12,8 +12,8 @@ struct EditorTabCloseButton: View { var isHoveringTab: Bool var isDragging: Bool var closeAction: () -> Void - @Binding var closeButtonGestureActive: Bool + var isDirty: Bool = false @Environment(\.colorScheme) var colorScheme @@ -22,24 +22,22 @@ struct EditorTabCloseButton: View { var tabBarStyle @State private var isPressingClose: Bool = false - @State private var isHoveringClose: Bool = false let buttonSize: CGFloat = 16 var body: some View { - HStack { + HStack(alignment: .center) { if tabBarStyle == .xcode { - Image(systemName: "xmark") - .font(.system(size: 11.5, weight: .regular, design: .rounded)) + Image(systemName: isDirty && !isHoveringTab ? "circlebadge.fill" : "xmark") + .font(.system(size: isDirty && !isHoveringTab ? 9.5 : 11.5, weight: .regular, design: .rounded)) .foregroundColor( isActive ? colorScheme == .dark ? .primary : Color(.controlAccentColor) : .secondary ) - .padding(.top, -0.5) } else { - Image(systemName: "xmark") + Image(systemName: isDirty && !isHoveringTab ? "circlebadge.fill" : "xmark") .font(.system(size: 9.5, weight: .medium, design: .rounded)) } } @@ -87,7 +85,7 @@ struct EditorTabCloseButton: View { } .accessibilityLabel(Text("Close")) // Only show when the mouse is hovering and there is no tab dragging. - .opacity(isHoveringTab && !isDragging ? 1 : 0) + .opacity((isHoveringTab || isDirty == true) && !isDragging ? 1 : 0) .animation(.easeInOut(duration: 0.08), value: isHoveringTab) .padding(.leading, 4) } diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift index 25a807282..022f534a9 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Combine struct EditorTabView: View { @@ -46,6 +47,8 @@ struct EditorTabView: View { /// By default, this value is `false`. When the root view is appeared, it turns `true`. @State private var isAppeared: Bool = false + @State private var isDirty: Bool = false + /// The expected tab width in native tab bar style. private var expectedWidth: CGFloat @@ -165,10 +168,14 @@ struct EditorTabView: View { isHoveringTab: isHovering, isDragging: draggingTabId != nil || onDragTabId != nil, closeAction: closeAction, - closeButtonGestureActive: $closeButtonGestureActive + closeButtonGestureActive: $closeButtonGestureActive, + isDirty: isDirty ) } .frame(maxWidth: .infinity, alignment: .leading) + .onReceive(item.fileDocument?.$isDirty.eraseToAnyPublisher() ?? Empty().eraseToAnyPublisher()) { _ in + isDirty = item.fileDocument?.isDirty ?? false + } } .opacity( // Inactive states for tab bar item content.