Skip to content

Commit 8013480

Browse files
authored
Add options to open panel too (#799)
1 parent cac0c29 commit 8013480

File tree

6 files changed

+129
-59
lines changed

6 files changed

+129
-59
lines changed

MarkEditKit/Sources/EditorTextEncoding.swift

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,35 @@ public enum EditorTextEncoding: CaseIterable, CustomStringConvertible, Codable {
6767
}
6868
}
6969

70-
public func decode(data: Data) -> String? {
71-
switch self {
72-
case .ascii: return String(data: data, encoding: .ascii)
73-
case .nonLossyASCII: return String(data: data, encoding: .nonLossyASCII)
74-
case .utf8: return String(data: data, encoding: .utf8)
75-
case .utf16: return String(data: data, encoding: .utf16)
76-
case .utf16BigEndian: return String(data: data, encoding: .utf16BigEndian)
77-
case .utf16LittleEndian: return String(data: data, encoding: .utf16LittleEndian)
78-
case .macOSRoman: return String(data: data, encoding: .macOSRoman)
79-
case .isoLatin1: return String(data: data, encoding: .isoLatin1)
80-
case .windowsLatin1: return String(data: data, encoding: .windowsCP1252)
81-
case .gb18030: return String(data: data, encoding: .GB_18030_2000)
82-
case .big5: return String(data: data, encoding: .big5)
83-
case .japaneseEUC: return String(data: data, encoding: .japaneseEUC)
84-
case .shiftJIS: return String(data: data, encoding: String.Encoding.shiftJIS)
85-
case .koreanEUC: return String(data: data, encoding: .EUC_KR)
70+
public func decode(data: Data, guessEncoding: Bool = false) -> String {
71+
let defaultResult = {
72+
switch self {
73+
case .ascii: return String(data: data, encoding: .ascii)
74+
case .nonLossyASCII: return String(data: data, encoding: .nonLossyASCII)
75+
case .utf8: return String(data: data, encoding: .utf8)
76+
case .utf16: return String(data: data, encoding: .utf16)
77+
case .utf16BigEndian: return String(data: data, encoding: .utf16BigEndian)
78+
case .utf16LittleEndian: return String(data: data, encoding: .utf16LittleEndian)
79+
case .macOSRoman: return String(data: data, encoding: .macOSRoman)
80+
case .isoLatin1: return String(data: data, encoding: .isoLatin1)
81+
case .windowsLatin1: return String(data: data, encoding: .windowsCP1252)
82+
case .gb18030: return String(data: data, encoding: .GB_18030_2000)
83+
case .big5: return String(data: data, encoding: .big5)
84+
case .japaneseEUC: return String(data: data, encoding: .japaneseEUC)
85+
case .shiftJIS: return String(data: data, encoding: String.Encoding.shiftJIS)
86+
case .koreanEUC: return String(data: data, encoding: .EUC_KR)
87+
}
88+
}()
89+
90+
if let defaultResult {
91+
return defaultResult
8692
}
93+
94+
if guessEncoding, let guessedResult = data.toString() {
95+
return guessedResult
96+
}
97+
98+
return data.asciiText()
8799
}
88100
}
89101

@@ -93,3 +105,17 @@ public extension EditorTextEncoding {
93105
Set([.nonLossyASCII, .utf16LittleEndian, .windowsLatin1, .big5, .shiftJIS])
94106
}
95107
}
108+
109+
// MARK: - Private
110+
111+
private extension Data {
112+
func asciiText(unsupported: Character = ".") -> String {
113+
reduce(into: "") { result, byte in
114+
if (byte >= 32 && byte < 127) || (byte >= 160 && byte < 255) || byte == 0x0A || byte == 0x09 {
115+
result.append(Character(UnicodeScalar(byte)))
116+
} else {
117+
result.append(unsupported)
118+
}
119+
}
120+
}
121+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// NSSavePanel+Extension.swift
3+
//
4+
// Created by cyan on 12/22/24.
5+
//
6+
7+
import AppKit
8+
import UniformTypeIdentifiers
9+
10+
public extension NSSavePanel {
11+
func enforceUniformType(_ type: UTType) {
12+
let otherFileTypesWereAllowed = allowsOtherFileTypes
13+
allowsOtherFileTypes = false // Must turn this off temporarily to enforce the file type
14+
allowedContentTypes = [type]
15+
16+
DispatchQueue.main.async {
17+
self.allowsOtherFileTypes = otherFileTypesWereAllowed
18+
}
19+
}
20+
}

MarkEditMac/Sources/Editor/Controllers/EditorViewController+Encoding.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import MarkEditKit
1111
extension EditorViewController {
1212
@objc func reopenWithEncoding(_ sender: NSMenuItem) {
1313
guard let encoding = sender.representedObject as? EditorTextEncoding else {
14-
return
14+
return Logger.assertFail("Invalid encoding: \(String(describing: sender.representedObject))")
1515
}
1616

17-
guard let data = document?.fileData, let string = encoding.decode(data: data) else {
18-
return
17+
guard let data = document?.fileData else {
18+
return Logger.assertFail("Missing fileData from: \(String(describing: document))")
1919
}
2020

21-
document?.stringValue = string
21+
document?.stringValue = encoding.decode(data: data)
2222
resetEditor()
2323
}
2424
}

MarkEditMac/Sources/Editor/Models/EditorDocument.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,13 @@ extension EditorDocument {
177177
}
178178

179179
if textBundle == nil {
180-
savePanel.accessoryView = EditorSaveOptionsView.wrapper(for: savePanel) { [weak self] in
181-
self?.suggestedTextEncoding = $0
180+
savePanel.accessoryView = EditorSaveOptionsView.wrapper(for: .all) { [weak self, weak savePanel] result in
181+
switch result {
182+
case .fileExtension(let value):
183+
savePanel?.enforceUniformType(value.uniformType)
184+
case .textEncoding(let value):
185+
self?.suggestedTextEncoding = value
186+
}
182187
}
183188
} else {
184189
savePanel.accessoryView = nil
@@ -195,9 +200,14 @@ extension EditorDocument {
195200
extension EditorDocument {
196201
override func read(from data: Data, ofType typeName: String) throws {
197202
DispatchQueue.global(qos: .userInitiated).async {
198-
let encoding = AppPreferences.General.defaultTextEncoding
199-
let newValue = encoding.decode(data: data) ?? data.toString() ?? ""
200-
guard self.stringValue != newValue else { return }
203+
let newValue = {
204+
if let encoding = AppDocumentController.suggestedTextEncoding {
205+
return encoding.decode(data: data)
206+
}
207+
208+
let encoding = AppPreferences.General.defaultTextEncoding
209+
return encoding.decode(data: data, guessEncoding: true)
210+
}()
201211

202212
DispatchQueue.main.async {
203213
self.fileData = data

MarkEditMac/Sources/Editor/Views/EditorSaveOptionsView.swift

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,54 +14,58 @@ import MarkEditKit
1414
Accessory view used in NSSavePanel to provide additional options.
1515
*/
1616
struct EditorSaveOptionsView: View {
17+
struct Options: OptionSet {
18+
let rawValue: Int
19+
static let fileExtension = Self(rawValue: 1 << 0)
20+
static let textEncoding = Self(rawValue: 1 << 1)
21+
static let all: Self = [.fileExtension, .textEncoding]
22+
}
23+
24+
enum Result {
25+
case fileExtension(value: NewFilenameExtension)
26+
case textEncoding(value: EditorTextEncoding)
27+
}
28+
1729
@State private var filenameExtension = AppPreferences.General.newFilenameExtension
1830
@State private var textEncoding = AppPreferences.General.defaultTextEncoding
1931

20-
private let extensionChanged: ((NewFilenameExtension) -> Void)
21-
private let encodingChanged: ((EditorTextEncoding) -> Void)
22-
23-
static func wrapper(for panel: NSSavePanel, encodingChanged: @escaping (EditorTextEncoding) -> Void) -> NSView {
24-
NSHostingView(rootView: Self(
25-
extensionChanged: {
26-
let allowsOtherFileTypes = panel.allowsOtherFileTypes == true
27-
panel.allowsOtherFileTypes = false
28-
panel.allowedContentTypes = [$0.uniformType]
32+
private let options: Options
33+
private let onValueChange: ((Result) -> Void)
2934

30-
// Must turn this off temporarily to enforce the file type
31-
DispatchQueue.main.async {
32-
panel.allowsOtherFileTypes = allowsOtherFileTypes
33-
}
34-
},
35-
encodingChanged: encodingChanged
36-
))
35+
static func wrapper(for options: Options, onValueChange: @escaping ((Result) -> Void)) -> NSView {
36+
NSHostingView(rootView: Self(options: options, onValueChange: onValueChange))
3737
}
3838

3939
var body: some View {
4040
SettingsForm(padding: 8) {
4141
Section {
42-
Picker(Localized.Document.filenameExtension, selection: $filenameExtension) {
43-
ForEach(NewFilenameExtension.allCases, id: \.self) {
44-
Text($0.rawValue).tag($0)
42+
if options.contains(.fileExtension) {
43+
Picker(Localized.Document.filenameExtension, selection: $filenameExtension) {
44+
ForEach(NewFilenameExtension.allCases, id: \.self) {
45+
Text($0.rawValue).tag($0)
46+
}
4547
}
48+
.onChange(of: filenameExtension) {
49+
onValueChange(.fileExtension(value: filenameExtension))
50+
}
51+
.formMenuPicker(minWidth: 120)
4652
}
47-
.onChange(of: filenameExtension) {
48-
extensionChanged(filenameExtension)
49-
}
50-
.formMenuPicker(minWidth: 120)
5153

52-
Picker(Localized.Document.textEncoding, selection: $textEncoding) {
53-
ForEach(EditorTextEncoding.allCases, id: \.self) {
54-
Text($0.description)
54+
if options.contains(.textEncoding) {
55+
Picker(Localized.Document.textEncoding, selection: $textEncoding) {
56+
ForEach(EditorTextEncoding.allCases, id: \.self) {
57+
Text($0.description)
5558

56-
if EditorTextEncoding.groupingCases.contains($0) {
57-
Divider()
59+
if EditorTextEncoding.groupingCases.contains($0) {
60+
Divider()
61+
}
5862
}
5963
}
64+
.onChange(of: textEncoding) {
65+
onValueChange(.textEncoding(value: textEncoding))
66+
}
67+
.formMenuPicker(minWidth: 120)
6068
}
61-
.onChange(of: textEncoding) {
62-
encodingChanged(textEncoding)
63-
}
64-
.formMenuPicker(minWidth: 120)
6569
}
6670
}
6771
.frame(maxWidth: .infinity)

MarkEditMac/Sources/Main/AppDocumentController.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,28 @@
66
//
77

88
import AppKit
9+
import MarkEditKit
910

1011
/**
1112
Subclass of `NSDocumentController` to allow customizations.
1213

1314
NSDocumentController.shared will be an instance of `AppDocumentController` at runtime.
1415
*/
1516
final class AppDocumentController: NSDocumentController {
16-
override func beginOpenPanel() async -> [URL]? {
17+
static var suggestedTextEncoding: EditorTextEncoding?
18+
19+
override func beginOpenPanel(_ openPanel: NSOpenPanel, forTypes inTypes: [String]?) async -> Int {
1720
if let defaultDirectory = AppRuntimeConfig.defaultOpenDirectory {
1821
setOpenPanelDirectory(defaultDirectory)
1922
}
2023

21-
return await super.beginOpenPanel()
24+
openPanel.accessoryView = EditorSaveOptionsView.wrapper(for: .textEncoding) { result in
25+
if case .textEncoding(let value) = result {
26+
Self.suggestedTextEncoding = value
27+
}
28+
}
29+
30+
Self.suggestedTextEncoding = nil
31+
return await super.beginOpenPanel(openPanel, forTypes: inTypes)
2232
}
2333
}

0 commit comments

Comments
 (0)