Skip to content

Commit

Permalink
Unsaved changes indicator in editor tab (#1441)
Browse files Browse the repository at this point in the history
* File tabs now indicate when unsaved changes are present.

* Unsaved changes tab indicator (#1449)

* Fixed SwiftLint errors and fixed inspector in Sonoma

* Use CEUndoManager in CEWorkspaceFile  (#1451)

* Unsaved changes tab indicator, fix onReceive and force re-render when item.fileDocument changed

* rename view to EditorFileTabCloseButton

* override updateChangeCount and add isDocumentEdited Publisher, remove isDirty property

* Use CEUndoManager in CodeFileDocument

* update CodeEditTextView version

* Fixed SwiftLint error

* Updated snapshot testing package

* Fixed small PR issue

---------

Co-authored-by: avinizhanov <[email protected]>
  • Loading branch information
austincondiff and avinizhanov authored Oct 17, 2023
1 parent 698b75e commit f20f28e
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 129 deletions.
14 changes: 11 additions & 3 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
04540D5E27DD08C300E91B77 /* WorkspaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */; };
04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */; };
0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */; };
04BC1CDE2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BC1CDD2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift */; };
04C3255B2801F86400C8DA2D /* ProjectNavigatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285FEC6D27FE4B4A00E57D53 /* ProjectNavigatorViewController.swift */; };
04C3255C2801F86900C8DA2D /* ProjectNavigatorMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285FEC7127FE4EEF00E57D53 /* ProjectNavigatorMenu.swift */; };
200412EF280F3EAC00BCAF5C /* HistoryInspectorNoHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 200412EE280F3EAC00BCAF5C /* HistoryInspectorNoHistoryView.swift */; };
Expand Down Expand Up @@ -343,6 +344,7 @@
B6041F4D29D7A4E9000F3454 /* SettingsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */; };
B6041F5229D7D6D6000F3454 /* SettingsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6041F5129D7D6D6000F3454 /* SettingsWindow.swift */; };
B60BE8BD297A167600841125 /* AcknowledgementRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */; };
B6152B802ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6152B7F2ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift */; };
B61A606129F188AB009B43F9 /* ExternalLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61A606029F188AB009B43F9 /* ExternalLink.swift */; };
B61A606929F4481A009B43F9 /* MonospacedFontPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */; };
B61DA9DF29D929E100BF4A43 /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61DA9DE29D929E100BF4A43 /* GeneralSettingsView.swift */; };
Expand Down Expand Up @@ -482,6 +484,7 @@
04660F6927E51E5C00477777 /* CodeEditWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditWindowController.swift; sourceTree = "<group>"; };
0468438427DC76E200F8E88E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceCodeFileView.swift; sourceTree = "<group>"; };
04BC1CDD2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorFileTabCloseButton.swift; sourceTree = "<group>"; };
200412EE280F3EAC00BCAF5C /* HistoryInspectorNoHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryInspectorNoHistoryView.swift; sourceTree = "<group>"; };
201169D62837B2E300F92B46 /* SourceControlNavigatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlNavigatorView.swift; sourceTree = "<group>"; };
201169D82837B31200F92B46 /* SourceControlSearchToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlSearchToolbar.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -798,6 +801,7 @@
B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPageView.swift; sourceTree = "<group>"; };
B6041F5129D7D6D6000F3454 /* SettingsWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindow.swift; sourceTree = "<group>"; };
B60BE8BC297A167600841125 /* AcknowledgementRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementRowView.swift; sourceTree = "<group>"; };
B6152B7F2ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditWindowControllerExtensions.swift; sourceTree = "<group>"; };
B61A606029F188AB009B43F9 /* ExternalLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalLink.swift; sourceTree = "<group>"; };
B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonospacedFontPicker.swift; sourceTree = "<group>"; };
B61DA9DE29D929E100BF4A43 /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1295,6 +1299,7 @@
children = (
043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */,
04660F6927E51E5C00477777 /* CodeEditWindowController.swift */,
B6152B7F2ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift */,
4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */,
);
path = Controllers;
Expand Down Expand Up @@ -2330,6 +2335,7 @@
58AFAA272933C65C00482B53 /* Models */,
B6C6A42F29771F7100A3D28F /* EditorTabBackground.swift */,
B6C6A42D29771A8D00A3D28F /* EditorTabButtonStyle.swift */,
04BC1CDD2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift */,
B6C6A429297716A500A3D28F /* EditorTabCloseButton.swift */,
587FB98F29C1246400B519DD /* EditorTabView.swift */,
);
Expand Down Expand Up @@ -3037,6 +3043,7 @@
587B9DA329300ABD00AC7927 /* SettingsTextEditor.swift in Sources */,
B6F0517B29D9E46400D72287 /* SourceControlSettingsView.swift in Sources */,
6C147C4D29A32AA30089B630 /* EditorView.swift in Sources */,
B6152B802ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift in Sources */,
587B9E7B29301D8F00AC7927 /* GitHubRouter.swift in Sources */,
201169E22837B3D800F92B46 /* SourceControlNavigatorChangesView.swift in Sources */,
850C631029D6B01D00E1444C /* SettingsView.swift in Sources */,
Expand Down Expand Up @@ -3204,6 +3211,7 @@
58D01C99293167DC00C5B6B4 /* String+MD5.swift in Sources */,
20EBB505280C329800F3A5DA /* HistoryInspectorItemView.swift in Sources */,
5878DAB2291D627C00DD95A3 /* EditorPathBarView.swift in Sources */,
04BC1CDE2AD9B4B000A83EA5 /* EditorFileTabCloseButton.swift in Sources */,
6C6BD70129CD172700235D17 /* ExtensionsListView.swift in Sources */,
043C321627E3201F006AE443 /* WorkspaceDocument.swift in Sources */,
58F2EAEC292FB2B0004A9BDE /* IgnoredFiles.swift in Sources */,
Expand Down Expand Up @@ -4145,8 +4153,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git";
requirement = {
kind = exactVersion;
version = 1.9.0;
kind = upToNextMinorVersion;
minimumVersion = 1.14.2;
};
};
58798288292ED15F0085B254 /* XCRemoteSwiftPackageReference "SwiftTerm" */ = {
Expand All @@ -4170,7 +4178,7 @@
repositoryURL = "https://github.com/CodeEditApp/CodeEditTextView";
requirement = {
kind = exactVersion;
version = 0.6.7;
version = 0.6.8;
};
};
6C0F3A3A2A1D0D5000223D19 /* XCRemoteSwiftPackageReference "CodeEditKit" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
{
"identity" : "codeedittextview",
"kind" : "remoteSourceControl",
"location" : "https://github.com/CodeEditApp/CodeEditTextView.git",
"location" : "https://github.com/CodeEditApp/CodeEditTextView",
"state" : {
"revision" : "7f130bd50bb9eb6bacf6a42700cce571ec82bd64",
"version" : "0.6.7"
"revision" : "6a04ca72975b25b28829e419a43cce5cabb97891",
"version" : "0.6.8"
}
},
{
Expand Down Expand Up @@ -194,8 +194,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing.git",
"state" : {
"revision" : "f8a9c997c3c1dab4e216a8ec9014e23144cbab37",
"version" : "1.9.0"
"revision" : "bb0ea08db8e73324fe6c3727f755ca41a23ff2f4",
"version" : "1.14.2"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "74203046135342e4a4a627476dd6caf8b28fe11b",
"version" : "509.0.0"
}
},
{
Expand Down Expand Up @@ -239,8 +248,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Wouter01/SwiftUI-WindowManagement",
"state" : {
"revision" : "03642ad06a3aa51e8284eb22146a208269cdc1ca",
"version" : "2.1.0"
"revision" : "adbebf5d7df325f3d7bf07dc832e5e162a9003f5",
"version" : "2.1.1"
}
},
{
Expand Down
14 changes: 13 additions & 1 deletion CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation
import SwiftUI
import UniformTypeIdentifiers
import Combine

/// An object containing all necessary information and actions for a specific file in the workspace
///
Expand Down Expand Up @@ -54,7 +55,18 @@ final class CEWorkspaceFile: Codable, Comparable, Hashable, Identifiable, Editor
/// If the item already is the top-level ``CEWorkspaceFile`` this returns `nil`.
var parent: CEWorkspaceFile?

var fileDocument: CodeFileDocument?
private let fileDocumentSubject = PassthroughSubject<Void, Never>()

var fileDocument: CodeFileDocument? {
didSet {
fileDocumentSubject.send()
}
}

/// Publisher for fileDocument property
var fileDocumentPublisher: AnyPublisher<Void, Never> {
fileDocumentSubject.eraseToAnyPublisher()
}

var fileIdentifier = UUID().uuidString

Expand Down
30 changes: 30 additions & 0 deletions CodeEdit/Features/CodeFile/CodeFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import UniformTypeIdentifiers
import QuickLookUI
import CodeEditTextView
import CodeEditLanguages
import Combine

enum CodeFileError: Error {
case failedToDecode
Expand Down Expand Up @@ -71,6 +72,13 @@ final class CodeFileDocument: NSDocument, ObservableObject, QLPreviewItem {

@Published var cursorPosition = (1, 1)

private let isDocumentEditedSubject = PassthroughSubject<Bool, Never>()

/// Publisher for isDocumentEdited property
var isDocumentEditedPublisher: AnyPublisher<Bool, Never> {
isDocumentEditedSubject.eraseToAnyPublisher()
}

// MARK: - NSDocument

override class var autosavesInPlace: Bool {
Expand Down Expand Up @@ -118,4 +126,26 @@ final class CodeFileDocument: NSDocument, ObservableObject, QLPreviewItem {
guard let content = String(data: data, encoding: .utf8) else { return }
self.content = content
}

/// Triggered when change occured
override func updateChangeCount(_ change: NSDocument.ChangeType) {
super.updateChangeCount(change)

if CodeFileDocument.autosavesInPlace {
return
}

self.isDocumentEditedSubject.send(self.isDocumentEdited)
}

/// Triggered when changes saved
override func updateChangeCount(withToken changeCountToken: Any, for saveOperation: NSDocument.SaveOperationType) {
super.updateChangeCount(withToken: changeCountToken, for: saveOperation)

if CodeFileDocument.autosavesInPlace {
return
}

self.isDocumentEditedSubject.send(self.isDocumentEdited)
}
}
15 changes: 7 additions & 8 deletions CodeEdit/Features/CodeFile/CodeFileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]()
Expand All @@ -45,6 +47,8 @@ struct CodeFileView: View {

private let systemFont: NSFont = .monospacedSystemFont(ofSize: 11, weight: .medium)

private let undoManager = CEUndoManager()

init(codeFile: CodeFileDocument, isEditable: Bool = true) {
self.codeFile = codeFile
self.isEditable = isEditable
Expand All @@ -62,13 +66,7 @@ struct CodeFileView: View {
}
.store(in: &cancellables)

codeFile
.$content
.dropFirst()
.sink { _ in
codeFile.updateChangeCount(.changeDone)
}
.store(in: &cancellables)
codeFile.undoManager = self.undoManager.manager
}

@State private var selectedTheme = ThemeModel.shared.selectedTheme ?? ThemeModel.shared.themes.first!
Expand Down Expand Up @@ -114,7 +112,8 @@ struct CodeFileView: View {
contentInsets: edgeInsets.nsEdgeInsets,
isEditable: isEditable,
letterSpacing: letterSpacing,
bracketPairHighlight: bracketPairHighlight
bracketPairHighlight: bracketPairHighlight,
undoManager: undoManager
)

.id(codeFile.fileURL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,30 +52,6 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs
fatalError("init(coder:) has not been implemented")
}

/// These are example items that added as commands to command palette
func registerCommands() {
CommandManager.shared.addCommand(
name: "Quick Open",
title: "Quick Open",
id: "quick_open",
command: CommandClosureWrapper(closure: { self.openQuickly(self) })
)

CommandManager.shared.addCommand(
name: "Toggle Navigator",
title: "Toggle Navigator",
id: "toggle_left_sidebar",
command: CommandClosureWrapper(closure: { self.toggleFirstPanel() })
)

CommandManager.shared.addCommand(
name: "Toggle Inspector",
title: "Toggle Inspector",
id: "toggle_right_sidebar",
command: CommandClosureWrapper(closure: { self.toggleLastPanel() })
)
}

private func setupSplitView(with workspace: WorkspaceDocument) {
let feedbackPerformer = NSHapticFeedbackManager.defaultPerformer
let splitVC = CodeEditSplitViewController(workspace: workspace, feedbackPerformer: feedbackPerformer)
Expand Down Expand Up @@ -240,7 +216,8 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs
}

@IBAction func saveDocument(_ sender: Any) {
getSelectedCodeFile()?.save(sender)
guard let codeFile = getSelectedCodeFile() else { return }
codeFile.save(sender)
workspace?.editorManager.activeEditor.temporaryTab = nil
}

Expand Down Expand Up @@ -310,44 +287,3 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs
}
}
}

extension CodeEditWindowController {
@objc
func toggleFirstPanel() {
guard let firstSplitView = splitViewController.splitViewItems.first else { return }
firstSplitView.animator().isCollapsed.toggle()
if let codeEditSplitVC = splitViewController as? CodeEditSplitViewController {
codeEditSplitVC.saveNavigatorCollapsedState(isCollapsed: firstSplitView.isCollapsed)
}
}

@objc
func toggleLastPanel() {
guard let lastSplitView = splitViewController.splitViewItems.last else { return }

if let toolbar = window?.toolbar,
lastSplitView.isCollapsed,
!toolbar.items.map(\.itemIdentifier).contains(.itemListTrackingSeparator) {
window?.toolbar?.insertItem(withItemIdentifier: .itemListTrackingSeparator, at: 4)
}
NSAnimationContext.runAnimationGroup { _ in
lastSplitView.animator().isCollapsed.toggle()
} completionHandler: { [weak self] in
if lastSplitView.isCollapsed {
self?.window?.animator().toolbar?.removeItem(at: 4)
}
}

if let codeEditSplitVC = splitViewController as? CodeEditSplitViewController {
codeEditSplitVC.saveInspectorCollapsedState(isCollapsed: lastSplitView.isCollapsed)
codeEditSplitVC.hideInspectorToolbarBackground()
}
}
}

extension NSToolbarItem.Identifier {
static let toggleFirstSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier("ToggleFirstSidebarItem")
static let toggleLastSidebarItem: NSToolbarItem.Identifier = NSToolbarItem.Identifier("ToggleLastSidebarItem")
static let itemListTrackingSeparator = NSToolbarItem.Identifier("ItemListTrackingSeparator")
static let branchPicker: NSToolbarItem.Identifier = NSToolbarItem.Identifier("BranchPicker")
}
Loading

0 comments on commit f20f28e

Please sign in to comment.