From 93be4993eb4fc4823388bece036129b4f699fa3f Mon Sep 17 00:00:00 2001
From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com>
Date: Mon, 19 Jun 2023 21:16:46 -0500
Subject: [PATCH] Inspector Text Editing Prefs File-Specific

---
 CodeEdit/Features/CodeFile/CodeFile.swift     | 13 ++++++
 CodeEdit/Features/CodeFile/CodeFileView.swift | 40 ++++++++----------
 .../Views/FileInspectorView.swift             | 41 ++++++++++++++++---
 .../TextEditingSettingsView.swift             | 18 +-------
 .../Views/IndentOptionView.swift              | 10 ++---
 .../Views/TabWidthOptionView.swift            | 14 ++-----
 6 files changed, 77 insertions(+), 59 deletions(-)

diff --git a/CodeEdit/Features/CodeFile/CodeFile.swift b/CodeEdit/Features/CodeFile/CodeFile.swift
index a1aff2149..36827c3dc 100644
--- a/CodeEdit/Features/CodeFile/CodeFile.swift
+++ b/CodeEdit/Features/CodeFile/CodeFile.swift
@@ -10,6 +10,7 @@ import Foundation
 import SwiftUI
 import UniformTypeIdentifiers
 import QuickLookUI
+import CodeEditTextView
 import CodeEditLanguages
 
 enum CodeFileError: Error {
@@ -28,6 +29,18 @@ final class CodeFileDocument: NSDocument, ObservableObject, QLPreviewItem {
     @Published
     var language: CodeLanguage?
 
+    /// Document-specific overriden indent option.
+    @Published
+    var indentOption: SettingsData.TextEditingSettings.IndentOption?
+
+    /// Document-specific overriden tab width.
+    @Published
+    var defaultTabWidth: Int?
+
+    /// Document-specific overriden line wrap preference.
+    @Published
+    var wrapLines: Bool?
+
     /*
      This is the main type of the document.
      For example, if the file is end with '.png', it will be an image,
diff --git a/CodeEdit/Features/CodeFile/CodeFileView.swift b/CodeEdit/Features/CodeFile/CodeFileView.swift
index 210dc4248..4eccc55d3 100644
--- a/CodeEdit/Features/CodeFile/CodeFileView.swift
+++ b/CodeEdit/Features/CodeFile/CodeFileView.swift
@@ -17,7 +17,7 @@ struct CodeFileView: View {
     private var codeFile: CodeFileDocument
 
     @AppSettings(\.textEditing.defaultTabWidth) var defaultTabWidth
-    @AppSettings(\.textEditing.indentOption) var settingsIndentOption
+    @AppSettings(\.textEditing.indentOption) var indentOption
     @AppSettings(\.textEditing.lineHeightMultiple) var lineHeightMultiple
     @AppSettings(\.textEditing.wrapLinesToEditorWidth) var wrapLinesToEditorWidth
     @AppSettings(\.textEditing.font) var settingsFont
@@ -88,17 +88,6 @@ struct CodeFileView: View {
         }
     }()
 
-    // Tab is a placeholder value, is overriden immediately in `init`.
-    @State
-    private var indentOption: IndentOption = {
-        switch Settings[\.textEditing].indentOption.indentType {
-        case .tab:
-            return .tab
-        case .spaces:
-            return .spaces(count: Settings[\.textEditing].indentOption.spaceCount)
-        }
-    }()
-
     @Environment(\.edgeInsets)
     private var edgeInsets
 
@@ -111,10 +100,10 @@ struct CodeFileView: View {
             language: getLanguage(),
             theme: selectedTheme.editor.editorTheme,
             font: font,
-            tabWidth: defaultTabWidth,
-            indentOption: indentOption,
+            tabWidth: codeFile.defaultTabWidth ?? defaultTabWidth,
+            indentOption: (codeFile.indentOption ?? indentOption).textViewOption(),
             lineHeight: lineHeightMultiple,
-            wrapLines: wrapLinesToEditorWidth,
+            wrapLines: codeFile.wrapLines ?? wrapLinesToEditorWidth,
             cursorPosition: $codeFile.cursorPosition,
             useThemeBackground: useThemeBackground,
             contentInsets: edgeInsets.nsEdgeInsets,
@@ -148,14 +137,6 @@ struct CodeFileView: View {
         .onChange(of: bracketHighlight) { _ in
             bracketPairHighlight = getBracketPairHighlight()
         }
-        .onChange(of: settingsIndentOption) { option in
-            switch option.indentType {
-            case .tab:
-                self.indentOption = .tab
-            case .spaces:
-                self.indentOption = .spaces(count: option.spaceCount)
-            }
-        }
     }
 
     private func getLanguage() -> CodeLanguage {
@@ -186,3 +167,16 @@ struct CodeFileView: View {
         }
     }
 }
+
+// This extension is kept here because it should not be used elsewhere in the app and may cause confusion
+// due to the similar type name from the CETV module.
+private extension SettingsData.TextEditingSettings.IndentOption {
+    func textViewOption() -> IndentOption {
+        switch self.indentType {
+        case .spaces:
+            return IndentOption.spaces(count: spaceCount)
+        case .tab:
+            return IndentOption.tab
+        }
+    }
+}
diff --git a/CodeEdit/Features/InspectorSidebar/Views/FileInspectorView.swift b/CodeEdit/Features/InspectorSidebar/Views/FileInspectorView.swift
index e8d587097..3ba470e72 100644
--- a/CodeEdit/Features/InspectorSidebar/Views/FileInspectorView.swift
+++ b/CodeEdit/Features/InspectorSidebar/Views/FileInspectorView.swift
@@ -23,9 +23,20 @@ struct FileInspectorView: View {
     @State
     private var fileName: String = ""
 
+    // File settings overrides
+
     @State
     private var language: CodeLanguage?
 
+    @State
+    var indentOption: SettingsData.TextEditingSettings.IndentOption = .init(indentType: .tab)
+
+    @State
+    var defaultTabWidth: Int = 0
+
+    @State
+    var wrapLines: Bool = false
+
     var body: some View {
         Group {
             if file != nil {
@@ -40,7 +51,7 @@ struct FileInspectorView: View {
                     Section("Text Settings") {
                         indentUsing
                         tabWidths
-                        wrapLines
+                        wrapLinesToggle
                     }
                 }
             } else {
@@ -51,11 +62,22 @@ struct FileInspectorView: View {
             file = tabManager.activeTabGroup.selected
             fileName = file?.name ?? ""
             language = file?.fileDocument?.language
+            indentOption = file?.fileDocument?.indentOption ?? textEditing.indentOption
+            defaultTabWidth = file?.fileDocument?.defaultTabWidth ?? textEditing.defaultTabWidth
+            wrapLines = file?.fileDocument?.wrapLines ?? textEditing.wrapLinesToEditorWidth
         }
         .onAppear {
             file = tabManager.activeTabGroup.selected
             fileName = file?.name ?? ""
             language = file?.fileDocument?.language
+            indentOption = file?.fileDocument?.indentOption ?? textEditing.indentOption
+            defaultTabWidth = file?.fileDocument?.defaultTabWidth ?? textEditing.defaultTabWidth
+            wrapLines = file?.fileDocument?.wrapLines ?? textEditing.wrapLinesToEditorWidth
+        }
+        .onChange(of: textEditing) { newValue in
+            indentOption = file?.fileDocument?.indentOption ?? newValue.indentOption
+            defaultTabWidth = file?.fileDocument?.defaultTabWidth ?? newValue.defaultTabWidth
+            wrapLines = file?.fileDocument?.wrapLines ?? newValue.wrapLinesToEditorWidth
         }
     }
 
@@ -147,15 +169,24 @@ struct FileInspectorView: View {
     }
 
     private var indentUsing: some View {
-        IndentOptionView()
+        IndentOptionView(indentOption: $indentOption)
+            .onChange(of: indentOption) { newValue in
+                file?.fileDocument?.indentOption = newValue == textEditing.indentOption ? nil : newValue
+            }
     }
 
     private var tabWidths: some View {
-        TabWidthOptionView()
+        TabWidthOptionView(defaultTabWidth: $defaultTabWidth)
+            .onChange(of: defaultTabWidth) { newValue in
+                file?.fileDocument?.defaultTabWidth = newValue == textEditing.defaultTabWidth ? nil : newValue
+            }
     }
 
-    private var wrapLines: some View {
-        Toggle("Wrap lines to editor width", isOn: $textEditing.wrapLinesToEditorWidth)
+    private var wrapLinesToggle: some View {
+        Toggle("Wrap lines to editor width", isOn: $wrapLines)
+            .onChange(of: wrapLines) { newValue in
+                file?.fileDocument?.wrapLines = newValue == textEditing.wrapLinesToEditorWidth ? nil : newValue
+            }
     }
 
     private func chooseNewFileLocation() -> URL? {
diff --git a/CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift b/CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift
index 14c5200ab..30077d9a0 100644
--- a/CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift
+++ b/CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift
@@ -86,26 +86,12 @@ private extension TextEditingSettingsView {
 
     @ViewBuilder
     private var indentOption: some View {
-        IndentOptionView()
+        IndentOptionView(indentOption: $textEditing.indentOption)
     }
 
     @ViewBuilder
     private var defaultTabWidth: some View {
-        HStack(alignment: .top) {
-            Stepper(
-                "Tab Width",
-                value: Binding<Double>(
-                    get: { Double(textEditing.defaultTabWidth) },
-                    set: { textEditing.defaultTabWidth = Int($0) }
-                ),
-                in: 1...8,
-                step: 1,
-                format: .number
-            )
-            Text("spaces")
-                .foregroundColor(.secondary)
-        }
-        .help("The visual width of tabs.")
+        TabWidthOptionView(defaultTabWidth: $textEditing.defaultTabWidth)
     }
 
     @ViewBuilder
diff --git a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Views/IndentOptionView.swift b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Views/IndentOptionView.swift
index 74cbd83bd..c49bfd098 100644
--- a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Views/IndentOptionView.swift
+++ b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Views/IndentOptionView.swift
@@ -8,23 +8,23 @@
 import SwiftUI
 
 struct IndentOptionView: View {
-    @AppSettings(\.textEditing) private var textEditing
+    @Binding var indentOption: SettingsData.TextEditingSettings.IndentOption
 
     var body: some View {
         Group {
-            Picker("Prefer Indent Using", selection: $textEditing.indentOption.indentType) {
+            Picker("Prefer Indent Using", selection: $indentOption.indentType) {
                 Text("Tabs")
                     .tag(SettingsData.TextEditingSettings.IndentOption.IndentType.tab)
                 Text("Spaces")
                     .tag(SettingsData.TextEditingSettings.IndentOption.IndentType.spaces)
             }
-            if textEditing.indentOption.indentType == .spaces {
+            if indentOption.indentType == .spaces {
                 HStack {
                     Stepper(
                         "Indent Width",
                         value: Binding<Double>(
-                            get: { Double(textEditing.indentOption.spaceCount) },
-                            set: { textEditing.indentOption.spaceCount = Int($0) }
+                            get: { Double(indentOption.spaceCount) },
+                            set: { indentOption.spaceCount = Int($0) }
                         ),
                         in: 0...10,
                         step: 1,
diff --git a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Views/TabWidthOptionView.swift b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Views/TabWidthOptionView.swift
index 2c9ca6251..526e92313 100644
--- a/CodeEdit/Features/Settings/Pages/TextEditingSettings/Views/TabWidthOptionView.swift
+++ b/CodeEdit/Features/Settings/Pages/TextEditingSettings/Views/TabWidthOptionView.swift
@@ -8,17 +8,17 @@
 import SwiftUI
 
 struct TabWidthOptionView: View {
-    @AppSettings(\.textEditing) private var textEditing
+    @Binding var defaultTabWidth: Int
 
     var body: some View {
         HStack(alignment: .top) {
             Stepper(
                 "Tab Width",
                 value: Binding<Double>(
-                    get: { Double(textEditing.defaultTabWidth) },
-                    set: { textEditing.defaultTabWidth = Int($0) }
+                    get: { Double(defaultTabWidth) },
+                    set: { defaultTabWidth = Int($0) }
                 ),
-                in: 1...8,
+                in: 1...16,
                 step: 1,
                 format: .number
             )
@@ -28,9 +28,3 @@ struct TabWidthOptionView: View {
         .help("The visual width of tabs.")
     }
 }
-
-struct TabWidthOptionView_Previews: PreviewProvider {
-    static var previews: some View {
-        TabWidthOptionView()
-    }
-}