Skip to content

Commit 0b5ad23

Browse files
committed
Implement Invisible Characters Setting
1 parent a51be20 commit 0b5ad23

File tree

10 files changed

+324
-55
lines changed

10 files changed

+324
-55
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
6C0824A12C5C0C9700A0751E /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6C0824A02C5C0C9700A0751E /* SwiftTerm */; };
2121
6C147C4529A329350089B630 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 6C147C4429A329350089B630 /* OrderedCollections */; };
2222
6C4E37FC2C73E00700AEE7B5 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6C4E37FB2C73E00700AEE7B5 /* SwiftTerm */; };
23+
6C50EF3B2DFC83E4007FE626 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C50EF3A2DFC83E4007FE626 /* CodeEditSourceEditor */; };
2324
6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 6C66C31229D05CDC00DE9ED2 /* GRDB */; };
2425
6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */; };
2526
6C6BD6F829CD14D100235D17 /* CodeEditKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F729CD14D100235D17 /* CodeEditKit */; };
@@ -184,6 +185,7 @@
184185
6C0824A12C5C0C9700A0751E /* SwiftTerm in Frameworks */,
185186
6C81916B29B41DD300B75C92 /* DequeModule in Frameworks */,
186187
6CB94D032CA1205100E8651C /* AsyncAlgorithms in Frameworks */,
188+
6C50EF3B2DFC83E4007FE626 /* CodeEditSourceEditor in Frameworks */,
187189
);
188190
runOnlyForDeploymentPostprocessing = 0;
189191
};
@@ -317,6 +319,7 @@
317319
6CB94D022CA1205100E8651C /* AsyncAlgorithms */,
318320
6CC00A8A2CBEF150004E8134 /* CodeEditSourceEditor */,
319321
6C73A6D22D4F1E550012D95C /* CodeEditSourceEditor */,
322+
6C50EF3A2DFC83E4007FE626 /* CodeEditSourceEditor */,
320323
);
321324
productName = CodeEdit;
322325
productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */;
@@ -419,7 +422,7 @@
419422
303E88462C276FD600EEA8D9 /* XCRemoteSwiftPackageReference "LanguageServerProtocol" */,
420423
6C4E37FA2C73E00700AEE7B5 /* XCRemoteSwiftPackageReference "SwiftTerm" */,
421424
6CB94D012CA1205100E8651C /* XCRemoteSwiftPackageReference "swift-async-algorithms" */,
422-
6CF368562DBBD274006A77FD /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */,
425+
6C50EF392DFC83E4007FE626 /* XCLocalSwiftPackageReference "../CodeEditSourceEditor" */,
423426
);
424427
preferredProjectObjectVersion = 55;
425428
productRefGroup = B658FB2D27DA9E0F00EA4DBD /* Products */;
@@ -1616,6 +1619,13 @@
16161619
};
16171620
/* End XCConfigurationList section */
16181621

1622+
/* Begin XCLocalSwiftPackageReference section */
1623+
6C50EF392DFC83E4007FE626 /* XCLocalSwiftPackageReference "../CodeEditSourceEditor" */ = {
1624+
isa = XCLocalSwiftPackageReference;
1625+
relativePath = ../CodeEditSourceEditor;
1626+
};
1627+
/* End XCLocalSwiftPackageReference section */
1628+
16191629
/* Begin XCRemoteSwiftPackageReference section */
16201630
2816F592280CF50500DD548B /* XCRemoteSwiftPackageReference "CodeEditSymbols" */ = {
16211631
isa = XCRemoteSwiftPackageReference;
@@ -1745,14 +1755,6 @@
17451755
version = 1.0.1;
17461756
};
17471757
};
1748-
6CF368562DBBD274006A77FD /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */ = {
1749-
isa = XCRemoteSwiftPackageReference;
1750-
repositoryURL = "https://github.com/CodeEditApp/CodeEditSourceEditor";
1751-
requirement = {
1752-
kind = exactVersion;
1753-
version = 0.13.2;
1754-
};
1755-
};
17561758
/* End XCRemoteSwiftPackageReference section */
17571759

17581760
/* Begin XCSwiftPackageProductDependency section */
@@ -1800,6 +1802,10 @@
18001802
package = 6C4E37FA2C73E00700AEE7B5 /* XCRemoteSwiftPackageReference "SwiftTerm" */;
18011803
productName = SwiftTerm;
18021804
};
1805+
6C50EF3A2DFC83E4007FE626 /* CodeEditSourceEditor */ = {
1806+
isa = XCSwiftPackageProductDependency;
1807+
productName = CodeEditSourceEditor;
1808+
};
18031809
6C66C31229D05CDC00DE9ED2 /* GRDB */ = {
18041810
isa = XCSwiftPackageProductDependency;
18051811
package = 6C66C31129D05CC800DE9ED2 /* XCRemoteSwiftPackageReference "GRDB.swift" */;

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

Lines changed: 9 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CodeEdit/Features/CodeEditUI/Views/KeyValueTable.swift

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ struct KeyValueItem: Identifiable, Equatable {
1313
let value: String
1414
}
1515

16-
private struct NewListTableItemView: View {
16+
private struct NewListTableItemView<HeaderView: View>: View {
1717
@Environment(\.dismiss)
1818
var dismiss
1919

@@ -24,17 +24,21 @@ private struct NewListTableItemView: View {
2424
let valueColumnName: String
2525
let newItemInstruction: String
2626
let validKeys: [String]
27-
let headerView: AnyView?
27+
let headerView: HeaderView?
2828
var completion: (String, String) -> Void
2929

3030
init(
31+
key: String? = nil,
32+
value: String? = nil,
3133
_ keyColumnName: String,
3234
_ valueColumnName: String,
3335
_ newItemInstruction: String,
3436
validKeys: [String],
35-
headerView: AnyView? = nil,
37+
headerView: HeaderView? = nil,
3638
completion: @escaping (String, String) -> Void
3739
) {
40+
self.key = key ?? ""
41+
self.value = value ?? ""
3842
self.keyColumnName = keyColumnName
3943
self.valueColumnName = valueColumnName
4044
self.newItemInstruction = newItemInstruction
@@ -62,7 +66,11 @@ private struct NewListTableItemView: View {
6266
TextField(valueColumnName, text: $value)
6367
.textFieldStyle(.plain)
6468
} header: {
65-
headerView
69+
if HeaderView.self == EmptyView.self {
70+
Text(newItemInstruction)
71+
} else {
72+
headerView
73+
}
6674
}
6775
}
6876
.formStyle(.grouped)
@@ -94,17 +102,18 @@ private struct NewListTableItemView: View {
94102
}
95103
}
96104

97-
struct KeyValueTable<Header: View>: View {
105+
struct KeyValueTable<Header: View, ActionBarView: View>: View {
98106
@Binding var items: [String: String]
99107

100108
let validKeys: [String]
101109
let keyColumnName: String
102110
let valueColumnName: String
103111
let newItemInstruction: String
104-
let header: () -> Header
112+
let newItemHeader: () -> Header
113+
let actionBarTrailing: () -> ActionBarView
105114

106-
@State private var showingModal = false
107-
@State private var selection: UUID?
115+
@State private var editingItem: KeyValueItem?
116+
@State private var selection: Set<UUID> = []
108117
@State private var tableItems: [KeyValueItem] = []
109118

110119
init(
@@ -113,14 +122,16 @@ struct KeyValueTable<Header: View>: View {
113122
keyColumnName: String,
114123
valueColumnName: String,
115124
newItemInstruction: String,
116-
@ViewBuilder header: @escaping () -> Header = { EmptyView() }
125+
@ViewBuilder newItemHeader: @escaping () -> Header = { EmptyView() },
126+
@ViewBuilder actionBarTrailing: @escaping () -> ActionBarView = { EmptyView() }
117127
) {
118128
self._items = items
119129
self.validKeys = validKeys
120130
self.keyColumnName = keyColumnName
121131
self.valueColumnName = valueColumnName
122132
self.newItemInstruction = newItemInstruction
123-
self.header = header
133+
self.newItemHeader = newItemHeader
134+
self.actionBarTrailing = actionBarTrailing
124135
}
125136

126137
var body: some View {
@@ -132,11 +143,24 @@ struct KeyValueTable<Header: View>: View {
132143
Text(item.value)
133144
}
134145
}
135-
.frame(height: 200)
146+
.contextMenu(
147+
forSelectionType: UUID.self,
148+
menu: { selectedItems in
149+
Button("Edit") {
150+
editItem(id: selectedItems.first)
151+
}
152+
Button("Remove") {
153+
removeItem(selectedItems)
154+
}
155+
},
156+
primaryAction: { selectedItems in
157+
editItem(id: selectedItems.first)
158+
}
159+
)
136160
.actionBar {
137161
HStack(spacing: 2) {
138162
Button {
139-
showingModal = true
163+
editingItem = KeyValueItem(key: "", value: "")
140164
} label: {
141165
Image(systemName: "plus")
142166
}
@@ -149,38 +173,64 @@ struct KeyValueTable<Header: View>: View {
149173
} label: {
150174
Image(systemName: "minus")
151175
}
152-
.disabled(selection == nil)
153-
.opacity(selection == nil ? 0.5 : 1)
176+
.disabled(selection.isEmpty)
177+
.opacity(selection.isEmpty ? 0.5 : 1)
178+
179+
Spacer()
180+
181+
actionBarTrailing()
154182
}
155-
Spacer()
156183
}
157-
.sheet(isPresented: $showingModal) {
184+
.sheet(item: $editingItem) { item in
158185
NewListTableItemView(
186+
key: item.key,
187+
value: item.value,
159188
keyColumnName,
160189
valueColumnName,
161190
newItemInstruction,
162191
validKeys: validKeys,
163-
headerView: AnyView(header())
192+
headerView: newItemHeader()
164193
) { key, value in
165194
items[key] = value
166-
updateTableItems()
167-
showingModal = false
195+
editingItem = nil
168196
}
169197
}
170198
.cornerRadius(6)
171-
.onAppear(perform: updateTableItems)
199+
.onAppear {
200+
updateTableItems(items)
201+
if let first = tableItems.first?.id {
202+
selection = [first]
203+
}
204+
selection = []
205+
}
206+
.onChange(of: items) { newValue in
207+
updateTableItems(newValue)
208+
}
172209
}
173210

174-
private func updateTableItems() {
175-
tableItems = items.map { KeyValueItem(key: $0.key, value: $0.value) }
211+
private func updateTableItems(_ newValue: [String: String]) {
212+
tableItems = items
213+
.sorted { $0.key < $1.key }
214+
.map { KeyValueItem(key: $0.key, value: $0.value) }
176215
}
177216

178217
private func removeItem() {
179-
guard let selectedId = selection else { return }
180-
if let selectedItem = tableItems.first(where: { $0.id == selectedId }) {
181-
items.removeValue(forKey: selectedItem.key)
182-
updateTableItems()
218+
removeItem(selection)
219+
self.selection.removeAll()
220+
}
221+
222+
private func removeItem(_ selection: Set<UUID>) {
223+
for selectedId in selection {
224+
if let selectedItem = tableItems.first(where: { $0.id == selectedId }) {
225+
items.removeValue(forKey: selectedItem.key)
226+
}
227+
}
228+
}
229+
230+
private func editItem(id: UUID?) {
231+
guard let id, let item = tableItems.first(where: { $0.id == id }) else {
232+
return
183233
}
184-
selection = nil
234+
editingItem = item
185235
}
186236
}

CodeEdit/Features/Editor/Views/CodeFileView.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ struct CodeFileView: View {
5454
var reformatAtColumn
5555
@AppSettings(\.textEditing.showReformattingGuide)
5656
var showReformattingGuide
57+
@AppSettings(\.textEditing.invisibleCharacters)
58+
var invisibleCharactersConfig
5759

5860
@Environment(\.colorScheme)
5961
private var colorScheme
@@ -141,7 +143,13 @@ struct CodeFileView: View {
141143
coordinators: textViewCoordinators,
142144
showMinimap: showMinimap,
143145
reformatAtColumn: reformatAtColumn,
144-
showReformattingGuide: showReformattingGuide
146+
showReformattingGuide: showReformattingGuide,
147+
invisibleCharactersConfig: .init(
148+
showSpaces: invisibleCharactersConfig.showSpaces,
149+
showTabs: invisibleCharactersConfig.showTabs,
150+
showLineEndings: invisibleCharactersConfig.showLineEndings,
151+
warningCharacters: Set(invisibleCharactersConfig.warningCharacters.keys)
152+
)
145153
)
146154
.id(codeFile.fileURL)
147155
.background {

CodeEdit/Features/Settings/Pages/DeveloperSettings/DeveloperSettingsView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ struct DeveloperSettingsView: View {
3434
Text(
3535
"Specify the absolute path to your LSP binary and its associated language."
3636
)
37+
} actionBarTrailing: {
38+
EmptyView()
3739
}
40+
.frame(minHeight: 96)
3841
} header: {
3942
Text("LSP Binaries")
4043
Text("Specify the language and the absolute path to the language server binary.")

0 commit comments

Comments
 (0)