Skip to content

Commit c38a42d

Browse files
committed
Separate Warning Chars and Invisibles
1 parent cb9d1e6 commit c38a42d

File tree

7 files changed

+198
-120
lines changed

7 files changed

+198
-120
lines changed

CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CodeEdit/Features/Editor/Views/CodeFileView.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ struct CodeFileView: View {
5656
var showReformattingGuide
5757
@AppSettings(\.textEditing.invisibleCharacters)
5858
var invisibleCharactersConfig
59+
@AppSettings(\.textEditing.warningCharacters)
60+
var warningCharacters
5961

6062
@Environment(\.colorScheme)
6163
private var colorScheme
@@ -144,7 +146,8 @@ struct CodeFileView: View {
144146
showMinimap: showMinimap,
145147
reformatAtColumn: reformatAtColumn,
146148
showReformattingGuide: showReformattingGuide,
147-
invisibleCharactersConfig: invisibleCharactersConfig.textViewOption()
149+
invisibleCharactersConfig: invisibleCharactersConfig.textViewOption(),
150+
warningCharacters: warningCharacters.textViewOption()
148151
)
149152
.id(codeFile.fileURL)
150153
.background {
@@ -216,8 +219,7 @@ private extension SettingsData.TextEditingSettings.InvisibleCharactersConfig {
216219
var config = InvisibleCharactersConfig(
217220
showSpaces: self.showSpaces,
218221
showTabs: self.showTabs,
219-
showLineEndings: self.showLineEndings,
220-
warningCharacters: Set(self.warningCharacters.keys)
222+
showLineEndings: self.showLineEndings
221223
)
222224
config.spaceReplacement = self.spaceReplacement
223225
config.tabReplacement = self.tabReplacement
@@ -228,3 +230,10 @@ private extension SettingsData.TextEditingSettings.InvisibleCharactersConfig {
228230
return config
229231
}
230232
}
233+
234+
private extension SettingsData.TextEditingSettings.WarningCharacters {
235+
func textViewOption() -> Set<UInt16> {
236+
guard self.enabled else { return [] }
237+
return Set(self.characters.keys)
238+
}
239+
}

CodeEdit/Features/Settings/Pages/TextEditingSettings/InvisiblesSettingsView.swift

Lines changed: 81 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -16,115 +16,102 @@ struct InvisiblesSettingsView: View {
1616
private var dismiss
1717

1818
var body: some View {
19-
NavigationStack {
20-
VStack(spacing: 0) {
21-
Form {
22-
Section {
23-
VStack {
24-
Toggle(isOn: $invisibleCharacters.showSpaces) { Text("Show Spaces") }
25-
if invisibleCharacters.showSpaces {
26-
TextField(
27-
text: $invisibleCharacters.spaceReplacement,
28-
prompt: Text("Default: \(Config.default.spaceReplacement)")
29-
) {
30-
Text("Character used to render spaces")
31-
.foregroundStyle(.secondary)
32-
.font(.caption)
33-
}
34-
.autocorrectionDisabled()
19+
VStack(spacing: 0) {
20+
Form {
21+
Section {
22+
VStack {
23+
Toggle(isOn: $invisibleCharacters.showSpaces) { Text("Show Spaces") }
24+
if invisibleCharacters.showSpaces {
25+
TextField(
26+
text: $invisibleCharacters.spaceReplacement,
27+
prompt: Text("Default: \(Config.default.spaceReplacement)")
28+
) {
29+
Text("Character used to render spaces")
30+
.foregroundStyle(.secondary)
31+
.font(.caption)
3532
}
33+
.autocorrectionDisabled()
3634
}
35+
}
3736

38-
VStack {
39-
Toggle(isOn: $invisibleCharacters.showTabs) { Text("Show Tabs") }
40-
if invisibleCharacters.showTabs {
41-
TextField(
42-
text: $invisibleCharacters.tabReplacement,
43-
prompt: Text("Default: \(Config.default.tabReplacement)")
44-
) {
45-
Text("Character used to render tabs")
46-
.foregroundStyle(.secondary)
47-
.font(.caption)
48-
}
49-
.autocorrectionDisabled()
37+
VStack {
38+
Toggle(isOn: $invisibleCharacters.showTabs) { Text("Show Tabs") }
39+
if invisibleCharacters.showTabs {
40+
TextField(
41+
text: $invisibleCharacters.tabReplacement,
42+
prompt: Text("Default: \(Config.default.tabReplacement)")
43+
) {
44+
Text("Character used to render tabs")
45+
.foregroundStyle(.secondary)
46+
.font(.caption)
5047
}
48+
.autocorrectionDisabled()
5149
}
50+
}
5251

53-
VStack {
54-
Toggle(isOn: $invisibleCharacters.showLineEndings) { Text("Show Line Endings") }
55-
if invisibleCharacters.showLineEndings {
56-
TextField(
57-
text: $invisibleCharacters.lineFeedReplacement,
58-
prompt: Text("Default: \(Config.default.lineFeedReplacement)")
59-
) {
60-
Text("Character used to render line feeds (\\n)")
61-
.foregroundStyle(.secondary)
62-
.font(.caption)
63-
}
64-
.autocorrectionDisabled()
52+
VStack {
53+
Toggle(isOn: $invisibleCharacters.showLineEndings) { Text("Show Line Endings") }
54+
if invisibleCharacters.showLineEndings {
55+
TextField(
56+
text: $invisibleCharacters.lineFeedReplacement,
57+
prompt: Text("Default: \(Config.default.lineFeedReplacement)")
58+
) {
59+
Text("Character used to render line feeds (\\n)")
60+
.foregroundStyle(.secondary)
61+
.font(.caption)
62+
}
63+
.autocorrectionDisabled()
6564

66-
TextField(
67-
text: $invisibleCharacters.carriageReturnReplacement,
68-
prompt: Text("Default: \(Config.default.carriageReturnReplacement)")
69-
) {
70-
Text("Character used to render carriage returns (Microsoft-style line endings)")
71-
.foregroundStyle(.secondary)
72-
.font(.caption)
73-
}
74-
.autocorrectionDisabled()
65+
TextField(
66+
text: $invisibleCharacters.carriageReturnReplacement,
67+
prompt: Text("Default: \(Config.default.carriageReturnReplacement)")
68+
) {
69+
Text("Character used to render carriage returns (Microsoft-style line endings)")
70+
.foregroundStyle(.secondary)
71+
.font(.caption)
72+
}
73+
.autocorrectionDisabled()
7574

76-
TextField(
77-
text: $invisibleCharacters.paragraphSeparatorReplacement,
78-
prompt: Text("Default: \(Config.default.paragraphSeparatorReplacement)")
79-
) {
80-
Text("Character used to render paragraph separators")
81-
.foregroundStyle(.secondary)
82-
.font(.caption)
83-
}
84-
.autocorrectionDisabled()
75+
TextField(
76+
text: $invisibleCharacters.paragraphSeparatorReplacement,
77+
prompt: Text("Default: \(Config.default.paragraphSeparatorReplacement)")
78+
) {
79+
Text("Character used to render paragraph separators")
80+
.foregroundStyle(.secondary)
81+
.font(.caption)
82+
}
83+
.autocorrectionDisabled()
8584

86-
TextField(
87-
text: $invisibleCharacters.lineSeparatorReplacement,
88-
prompt: Text("Default: \(Config.default.lineSeparatorReplacement)")
89-
) {
90-
Text("Character used to render line separators")
91-
.foregroundStyle(.secondary)
92-
.font(.caption)
93-
}
94-
.autocorrectionDisabled()
85+
TextField(
86+
text: $invisibleCharacters.lineSeparatorReplacement,
87+
prompt: Text("Default: \(Config.default.lineSeparatorReplacement)")
88+
) {
89+
Text("Character used to render line separators")
90+
.foregroundStyle(.secondary)
91+
.font(.caption)
9592
}
93+
.autocorrectionDisabled()
9694
}
97-
} header: {
98-
Text("Invisible Characters")
99-
Text("Toggle whitespace symbols CodeEdit will render with replacement characters.")
100-
}
101-
.textFieldStyle(.roundedBorder)
102-
Section {
103-
InvisibleCharacterWarningList(items: $invisibleCharacters.warningCharacters)
104-
} header: {
105-
Text("Warning Characters")
106-
Text(
107-
"CodeEdit can help identify invisible or ambiguous characters, such as zero-width spaces," +
108-
" directional quotes, and more. These will appear with a red block highlighting them." +
109-
"You can disable characters or add more here."
110-
)
11195
}
96+
} header: {
97+
Text("Invisible Characters")
98+
Text("Toggle whitespace symbols CodeEdit will render with replacement characters.")
11299
}
113-
.formStyle(.grouped)
114-
Divider()
115-
HStack {
116-
Spacer()
117-
Button {
118-
dismiss()
119-
} label: {
120-
Text("Done")
121-
.frame(minWidth: 56)
122-
}
123-
.buttonStyle(.borderedProminent)
100+
.textFieldStyle(.roundedBorder)
101+
}
102+
.formStyle(.grouped)
103+
Divider()
104+
HStack {
105+
Spacer()
106+
Button {
107+
dismiss()
108+
} label: {
109+
Text("Done")
110+
.frame(minWidth: 56)
124111
}
125-
.padding()
112+
.buttonStyle(.borderedProminent)
126113
}
127-
.navigationTitle("Invisible Characters")
114+
.padding()
128115
}
129116
}
130117
}

CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ extension SettingsData {
3232
"Reformat at Column",
3333
"Show Reformatting Guide",
3434
"Invisibles",
35-
"Show whitespace",
35+
"Warning Characters"
3636
]
3737
if #available(macOS 14.0, *) {
3838
keys.append("System Cursor")
@@ -86,6 +86,9 @@ extension SettingsData {
8686

8787
var invisibleCharacters: InvisibleCharactersConfig = .default
8888

89+
/// Map of unicode character codes to a note about them
90+
var warningCharacters: WarningCharacters = .default
91+
8992
/// Default initializer
9093
init() {
9194
self.populateCommands()
@@ -144,6 +147,10 @@ extension SettingsData {
144147
InvisibleCharactersConfig.self,
145148
forKey: .invisibleCharacters
146149
) ?? .default
150+
self.warningCharacters = try container.decodeIfPresent(
151+
WarningCharacters.self,
152+
forKey: .warningCharacters
153+
) ?? .default
147154

148155
self.populateCommands()
149156
}
@@ -237,24 +244,7 @@ extension SettingsData {
237244
enabled: false,
238245
showSpaces: true,
239246
showTabs: true,
240-
showLineEndings: true,
241-
warningCharacters: [
242-
0x0003: "End of text",
243-
244-
0x00A0: "Non-breaking space",
245-
0x202F: "Narrow non-breaking space",
246-
0x200B: "Zero-width space",
247-
0x200C: "Zero-width non-joiner",
248-
0x2029: "Paragraph separator",
249-
250-
0x2013: "Em-dash",
251-
0x00AD: "Soft hyphen",
252-
253-
0x2018: "Left single quote",
254-
0x2019: "Right single quote",
255-
0x201C: "Left double quote",
256-
0x201D: "Right double quote",
257-
]
247+
showLineEndings: true
258248
)
259249
}()
260250

@@ -272,9 +262,29 @@ extension SettingsData {
272262
var lineFeedReplacement: String = "¬"
273263
var paragraphSeparatorReplacement: String = ""
274264
var lineSeparatorReplacement: String = ""
265+
}
266+
267+
struct WarningCharacters: Equatable, Hashable, Codable {
268+
static let `default`: WarningCharacters = WarningCharacters(enabled: true, characters: [
269+
0x0003: "End of text",
275270

276-
// Map of unicode character codes to a note about them
277-
var warningCharacters: [UInt16: String]
271+
0x00A0: "Non-breaking space",
272+
0x202F: "Narrow non-breaking space",
273+
0x200B: "Zero-width space",
274+
0x200C: "Zero-width non-joiner",
275+
0x2029: "Paragraph separator",
276+
277+
0x2013: "Em-dash",
278+
0x00AD: "Soft hyphen",
279+
280+
0x2018: "Left single quote",
281+
0x2019: "Right single quote",
282+
0x201C: "Left double quote",
283+
0x201D: "Right double quote",
284+
])
285+
286+
var enabled: Bool
287+
var characters: [UInt16: String]
278288
}
279289
}
280290

CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct TextEditingSettingsView: View {
1313
var textEditing
1414

1515
@State private var isShowingInvisibleCharacterSettings = false
16+
@State private var isShowingWarningCharactersSettings = false
1617

1718
var body: some View {
1819
SettingsForm {
@@ -41,6 +42,7 @@ struct TextEditingSettingsView: View {
4142
}
4243
Section {
4344
invisibles
45+
warningCharacters
4446
}
4547
}
4648
}
@@ -252,4 +254,27 @@ private extension TextEditingSettingsView {
252254
InvisiblesSettingsView(invisibleCharacters: $textEditing.invisibleCharacters)
253255
}
254256
}
257+
258+
@ViewBuilder private var warningCharacters: some View {
259+
HStack {
260+
Text("Show Warning Characters")
261+
Spacer()
262+
Toggle(isOn: $textEditing.warningCharacters.enabled, label: { EmptyView() })
263+
Button {
264+
isShowingWarningCharactersSettings = true
265+
} label: {
266+
Text("Configure...")
267+
}
268+
.disabled(textEditing.warningCharacters.enabled == false)
269+
}
270+
.contentShape(Rectangle())
271+
.onTapGesture {
272+
if textEditing.warningCharacters.enabled {
273+
isShowingWarningCharactersSettings = true
274+
}
275+
}
276+
.sheet(isPresented: $isShowingWarningCharactersSettings) {
277+
WarningCharactersView(warningCharacters: $textEditing.warningCharacters)
278+
}
279+
}
255280
}

CodeEdit/Features/Settings/Views/InvisibleCharacterWarningList.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct InvisibleCharacterWarningList: View {
4040
Button {
4141
// Add defaults without removing user's data. We do still override notes here.
4242
items = items.merging(
43-
SettingsData.TextEditingSettings.InvisibleCharactersConfig.default.warningCharacters,
43+
SettingsData.TextEditingSettings.WarningCharacters.default.characters,
4444
uniquingKeysWith: { _, defaults in
4545
defaults
4646
}

0 commit comments

Comments
 (0)