Skip to content

Commit f2caddf

Browse files
Developer settings menu (#1733)
* Added LSP binaries settings page * Fix icon colors * Renamed 'LSP Binaries' to 'Developer Settings' * Update KeyValueTable instructions, small refactors * Bug fix * Form styling for the modal * Toggle developer menu on F12 * Properly deinit event monitor * Refactor * Added header to modal and changed text field style * Fixed spacing on modal buttons
1 parent aa7454e commit f2caddf

File tree

8 files changed

+341
-9
lines changed

8 files changed

+341
-9
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@
6464
3000516A2BBD3A8200A98562 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300051692BBD3A8200A98562 /* ServiceType.swift */; };
6565
3000516C2BBD3A9500A98562 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */; };
6666
3026F50F2AC006C80061227E /* InspectorAreaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */; };
67+
30AB4EBB2BF718A100ED4431 /* DeveloperSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */; };
68+
30AB4EBD2BF71CA800ED4431 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */; };
69+
30AB4EC22BF7253200ED4431 /* KeyValueTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */; };
6770
30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */; };
6871
3E0196732A3921AC002648D8 /* codeedit_shell_integration.zsh in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */; };
6972
3E01967A2A392B45002648D8 /* codeedit_shell_integration.bash in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */; };
@@ -633,6 +636,9 @@
633636
300051692BBD3A8200A98562 /* ServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceType.swift; sourceTree = "<group>"; };
634637
3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = "<group>"; };
635638
3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorAreaViewModel.swift; sourceTree = "<group>"; };
639+
30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettings.swift; sourceTree = "<group>"; };
640+
30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsView.swift; sourceTree = "<group>"; };
641+
30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueTable.swift; sourceTree = "<group>"; };
636642
30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSidebarViewModel.swift; sourceTree = "<group>"; };
637643
3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.zsh; sourceTree = "<group>"; };
638644
3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.bash; sourceTree = "<group>"; };
@@ -1327,6 +1333,23 @@
13271333
path = ViewModels;
13281334
sourceTree = "<group>";
13291335
};
1336+
30AB4EB72BF7170B00ED4431 /* DeveloperSettings */ = {
1337+
isa = PBXGroup;
1338+
children = (
1339+
30AB4EB92BF7189300ED4431 /* Models */,
1340+
30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */,
1341+
);
1342+
path = DeveloperSettings;
1343+
sourceTree = "<group>";
1344+
};
1345+
30AB4EB92BF7189300ED4431 /* Models */ = {
1346+
isa = PBXGroup;
1347+
children = (
1348+
30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */,
1349+
);
1350+
path = Models;
1351+
sourceTree = "<group>";
1352+
};
13301353
3E0196712A392170002648D8 /* ShellIntegration */ = {
13311354
isa = PBXGroup;
13321355
children = (
@@ -1767,6 +1790,7 @@
17671790
587B9D8629300ABD00AC7927 /* Views */ = {
17681791
isa = PBXGroup;
17691792
children = (
1793+
30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */,
17701794
B62AEDA92A1FCBE5009A9F52 /* AreaTabBar.swift */,
17711795
B65B10EB2B073913002852CF /* CEContentUnavailableView.swift */,
17721796
B65B10FA2B08B054002852CF /* Divided.swift */,
@@ -2563,16 +2587,17 @@
25632587
B61DA9DD29D929BF00BF4A43 /* Pages */ = {
25642588
isa = PBXGroup;
25652589
children = (
2566-
B664C3AD2B965F4500816B4E /* NavigationSettings */,
2567-
B61DA9E129D929F900BF4A43 /* GeneralSettings */,
25682590
B6E41C6E29DD15540088F9F4 /* AccountsSettings */,
2569-
58F2EAAE292FB2B0004A9BDE /* ThemeSettings */,
2570-
B6EA1FF329DA37D3001BF195 /* TextEditingSettings */,
2571-
5B698A082B262F8400DE9392 /* SearchSettings */,
2591+
30AB4EB72BF7170B00ED4431 /* DeveloperSettings */,
2592+
B61DA9E129D929F900BF4A43 /* GeneralSettings */,
25722593
B6CF632629E5417C0085880A /* Keybindings */,
25732594
B6F0516E29D9E35300D72287 /* LocationsSettings */,
2595+
B664C3AD2B965F4500816B4E /* NavigationSettings */,
2596+
5B698A082B262F8400DE9392 /* SearchSettings */,
25742597
B6F0516D29D9E34200D72287 /* SourceControlSettings */,
25752598
B6F0516C29D9E32700D72287 /* TerminalSettings */,
2599+
B6EA1FF329DA37D3001BF195 /* TextEditingSettings */,
2600+
58F2EAAE292FB2B0004A9BDE /* ThemeSettings */,
25762601
);
25772602
path = Pages;
25782603
sourceTree = "<group>";
@@ -3459,6 +3484,7 @@
34593484
5882252D292C280D00E83CDE /* UtilityAreaSplitTerminalButton.swift in Sources */,
34603485
58798238292E30B90085B254 /* FeedbackWindowController.swift in Sources */,
34613486
587B9E6C29301D8F00AC7927 /* GitLabNamespace.swift in Sources */,
3487+
30AB4EC22BF7253200ED4431 /* KeyValueTable.swift in Sources */,
34623488
6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */,
34633489
587B9E8729301D8F00AC7927 /* GitHubRepositories.swift in Sources */,
34643490
6CE6226B2A2A1C730013085C /* UtilityAreaTab.swift in Sources */,
@@ -3584,6 +3610,7 @@
35843610
04BA7C0E2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift in Sources */,
35853611
615AA21A2B0CFD480013FCCC /* LazyStringLoader.swift in Sources */,
35863612
6CAAF68A29BC9C2300A1F48A /* (null) in Sources */,
3613+
30AB4EBD2BF71CA800ED4431 /* DeveloperSettingsView.swift in Sources */,
35873614
6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */,
35883615
6CFF967629BEBCD900182D6F /* FileCommands.swift in Sources */,
35893616
B60718462B17DC15009CDAB4 /* RepoOutlineGroupItem.swift in Sources */,
@@ -3606,6 +3633,7 @@
36063633
5878DA872918642F00DD95A3 /* AcknowledgementsViewModel.swift in Sources */,
36073634
B6E41C7929DE02800088F9F4 /* AccountSelectionView.swift in Sources */,
36083635
6CA1AE952B46950000378EAB /* EditorInstance.swift in Sources */,
3636+
30AB4EBB2BF718A100ED4431 /* DeveloperSettings.swift in Sources */,
36093637
B6C4F2A92B3CB00100B2B140 /* CommitDetailsHeaderView.swift in Sources */,
36103638
B6EA1FFB29DB78F6001BF195 /* ThemeSettingsThemeDetails.swift in Sources */,
36113639
587B9E7029301D8F00AC7927 /* GitLabUser.swift in Sources */,
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
//
2+
// KeyValueTable.swift
3+
// CodeEdit
4+
//
5+
// Created by Abe Malla on 5/16/24.
6+
//
7+
8+
import SwiftUI
9+
10+
struct KeyValueItem: Identifiable, Equatable {
11+
let id = UUID()
12+
let key: String
13+
let value: String
14+
}
15+
16+
private struct NewListTableItemView: View {
17+
@Environment(\.dismiss)
18+
var dismiss
19+
20+
@State private var key = ""
21+
@State private var value = ""
22+
23+
let keyColumnName: String
24+
let valueColumnName: String
25+
let newItemInstruction: String
26+
let headerView: AnyView?
27+
var completion: (String, String) -> Void
28+
29+
init(
30+
_ keyColumnName: String,
31+
_ valueColumnName: String,
32+
_ newItemInstruction: String,
33+
headerView: AnyView? = nil,
34+
completion: @escaping (String, String) -> Void
35+
) {
36+
self.keyColumnName = keyColumnName
37+
self.valueColumnName = valueColumnName
38+
self.newItemInstruction = newItemInstruction
39+
self.headerView = headerView
40+
self.completion = completion
41+
}
42+
43+
var body: some View {
44+
VStack(spacing: 0) {
45+
Form {
46+
Section {
47+
TextField(keyColumnName, text: $key)
48+
.textFieldStyle(.plain)
49+
TextField(valueColumnName, text: $value)
50+
.textFieldStyle(.plain)
51+
} header: {
52+
headerView
53+
}
54+
}
55+
.formStyle(.grouped)
56+
.scrollDisabled(true)
57+
.scrollContentBackground(.hidden)
58+
.onSubmit {
59+
if !key.isEmpty && !value.isEmpty {
60+
completion(key, value)
61+
}
62+
}
63+
64+
HStack {
65+
Spacer()
66+
Button("Cancel") {
67+
dismiss()
68+
}
69+
Button("Add") {
70+
if !key.isEmpty && !value.isEmpty {
71+
completion(key, value)
72+
}
73+
}
74+
.buttonStyle(.borderedProminent)
75+
.disabled(key.isEmpty || value.isEmpty)
76+
}
77+
.padding(.horizontal, 20)
78+
// .padding(.top, 2)
79+
.padding(.bottom, 20)
80+
}
81+
.frame(maxWidth: 480)
82+
}
83+
}
84+
85+
struct KeyValueTable<Header: View>: View {
86+
@Binding var items: [String: String]
87+
88+
let keyColumnName: String
89+
let valueColumnName: String
90+
let newItemInstruction: String
91+
let header: () -> Header
92+
93+
@State private var showingModal = false
94+
@State private var selection: UUID?
95+
@State private var tableItems: [KeyValueItem] = []
96+
97+
init(
98+
items: Binding<[String: String]>,
99+
keyColumnName: String,
100+
valueColumnName: String,
101+
newItemInstruction: String,
102+
@ViewBuilder header: @escaping () -> Header = { EmptyView() }
103+
) {
104+
self._items = items
105+
self.keyColumnName = keyColumnName
106+
self.valueColumnName = valueColumnName
107+
self.newItemInstruction = newItemInstruction
108+
self.header = header
109+
}
110+
111+
var body: some View {
112+
VStack {
113+
Table(tableItems, selection: $selection) {
114+
TableColumn(keyColumnName) { item in
115+
Text(item.key)
116+
}
117+
TableColumn(valueColumnName) { item in
118+
Text(item.value)
119+
}
120+
}
121+
.frame(height: 200)
122+
.actionBar {
123+
HStack(spacing: 2) {
124+
Button {
125+
showingModal = true
126+
} label: {
127+
Image(systemName: "plus")
128+
}
129+
130+
Divider()
131+
.frame(minHeight: 15)
132+
133+
Button {
134+
removeItem()
135+
} label: {
136+
Image(systemName: "minus")
137+
}
138+
.disabled(selection == nil)
139+
.opacity(selection == nil ? 0.5 : 1)
140+
}
141+
Spacer()
142+
}
143+
.sheet(isPresented: $showingModal) {
144+
NewListTableItemView(
145+
keyColumnName,
146+
valueColumnName,
147+
newItemInstruction,
148+
headerView: AnyView(header())
149+
) { key, value in
150+
items[key] = value
151+
updateTableItems()
152+
showingModal = false
153+
}
154+
}
155+
.cornerRadius(6)
156+
.onAppear(perform: updateTableItems)
157+
}
158+
}
159+
160+
private func updateTableItems() {
161+
tableItems = items.map { KeyValueItem(key: $0.key, value: $0.value) }
162+
}
163+
164+
private func removeItem() {
165+
guard let selectedId = selection else { return }
166+
if let selectedItem = tableItems.first(where: { $0.id == selectedId }) {
167+
items.removeValue(forKey: selectedItem.key)
168+
updateTableItems()
169+
}
170+
selection = nil
171+
}
172+
}

CodeEdit/Features/Settings/Models/SettingsData.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,12 @@ struct SettingsData: Codable, Hashable {
4747
/// The global settings for keybindings
4848
var keybindings: KeybindingsSettings = .init()
4949

50-
/// Searh Settings
50+
/// Search Settings
5151
var search: SearchSettings = .init()
5252

53+
/// Developer settings for CodeEdit developers
54+
var developerSettings: DeveloperSettings = .init()
55+
5356
/// Default initializer
5457
init() {}
5558

@@ -71,6 +74,9 @@ struct SettingsData: Codable, Hashable {
7174
KeybindingsSettings.self,
7275
forKey: .keybindings
7376
) ?? .init()
77+
self.developerSettings = try container.decodeIfPresent(
78+
DeveloperSettings.self, forKey: .developerSettings
79+
) ?? .init()
7480
}
7581

7682
// swiftlint:disable cyclomatic_complexity
@@ -96,6 +102,8 @@ struct SettingsData: Codable, Hashable {
96102
sourceControl.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
97103
case .location:
98104
LocationsSettings().searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
105+
case .developer:
106+
developerSettings.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
99107
case .behavior: return [.init(name, settingName: "Error")]
100108
case .components: return [.init(name, settingName: "Error")]
101109
case .keybindings: return [.init(name, settingName: "Error")]

CodeEdit/Features/Settings/Models/SettingsPage.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct SettingsPage: Hashable, Equatable, Identifiable {
3232
case components = "Components"
3333
case location = "Locations"
3434
case advanced = "Advanced"
35+
case developer = "Developer"
3536
}
3637

3738
let id: UUID = UUID()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ extension SettingsData {
1111

1212
/// The global settings for text editing
1313
struct AccountsSettings: Codable, Hashable, SearchableSettingsPage {
14-
/// An integer indicating how many spaces a `tab` will generate
14+
/// The list of git accounts the user has saved
1515
var sourceControlAccounts: GitAccounts = .init()
1616

1717
/// The search keys
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// DeveloperSettingsView.swift
3+
// CodeEdit
4+
//
5+
// Created by Abe Malla on 5/16/24.
6+
//
7+
8+
import SwiftUI
9+
10+
/// A view that implements the Developer settings section
11+
struct DeveloperSettingsView: View {
12+
@AppSettings(\.developerSettings.lspBinaries)
13+
var lspBinaries
14+
15+
var body: some View {
16+
SettingsForm {
17+
Section {
18+
KeyValueTable(
19+
items: $lspBinaries,
20+
keyColumnName: "Language",
21+
valueColumnName: "Language Server Path",
22+
newItemInstruction: "Add a language server"
23+
) {
24+
Text("Add a language server")
25+
Text(
26+
"Specify the absolute path to your LSP binary and its associated language."
27+
)
28+
}
29+
} header: {
30+
Text("LSP Binaries")
31+
Text("Specify the language and the absolute path to the language server binary.")
32+
}
33+
}
34+
}
35+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// DeveloperSettings.swift
3+
// CodeEdit
4+
//
5+
// Created by Abe Malla on 5/15/24.
6+
//
7+
8+
import Foundation
9+
10+
extension SettingsData {
11+
struct DeveloperSettings: Codable, Hashable, SearchableSettingsPage {
12+
13+
/// The search keys
14+
var searchKeys: [String] {
15+
[
16+
"Developer",
17+
"Language Server Protocol",
18+
"LSP Binaries"
19+
]
20+
.map { NSLocalizedString($0, comment: "") }
21+
}
22+
23+
/// A dictionary that stores a file type and a path to an LSP binary
24+
var lspBinaries: [String: String] = [:]
25+
26+
/// Default initializer
27+
init() {}
28+
29+
/// Explicit decoder init for setting default values when key is not present in `JSON`
30+
init(from decoder: Decoder) throws {
31+
let container = try decoder.container(keyedBy: CodingKeys.self)
32+
33+
self.lspBinaries = try container.decodeIfPresent(
34+
[String: String].self,
35+
forKey: .lspBinaries
36+
) ?? [:]
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)