Skip to content

Commit

Permalink
Developer settings menu (#1733)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
FastestMolasses authored May 26, 2024
1 parent aa7454e commit f2caddf
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 9 deletions.
38 changes: 33 additions & 5 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
3000516A2BBD3A8200A98562 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300051692BBD3A8200A98562 /* ServiceType.swift */; };
3000516C2BBD3A9500A98562 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */; };
3026F50F2AC006C80061227E /* InspectorAreaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */; };
30AB4EBB2BF718A100ED4431 /* DeveloperSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */; };
30AB4EBD2BF71CA800ED4431 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */; };
30AB4EC22BF7253200ED4431 /* KeyValueTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */; };
30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */; };
3E0196732A3921AC002648D8 /* codeedit_shell_integration.zsh in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */; };
3E01967A2A392B45002648D8 /* codeedit_shell_integration.bash in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */; };
Expand Down Expand Up @@ -633,6 +636,9 @@
300051692BBD3A8200A98562 /* ServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceType.swift; sourceTree = "<group>"; };
3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = "<group>"; };
3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorAreaViewModel.swift; sourceTree = "<group>"; };
30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettings.swift; sourceTree = "<group>"; };
30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsView.swift; sourceTree = "<group>"; };
30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueTable.swift; sourceTree = "<group>"; };
30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSidebarViewModel.swift; sourceTree = "<group>"; };
3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.zsh; sourceTree = "<group>"; };
3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.bash; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1327,6 +1333,23 @@
path = ViewModels;
sourceTree = "<group>";
};
30AB4EB72BF7170B00ED4431 /* DeveloperSettings */ = {
isa = PBXGroup;
children = (
30AB4EB92BF7189300ED4431 /* Models */,
30AB4EBC2BF71CA800ED4431 /* DeveloperSettingsView.swift */,
);
path = DeveloperSettings;
sourceTree = "<group>";
};
30AB4EB92BF7189300ED4431 /* Models */ = {
isa = PBXGroup;
children = (
30AB4EBA2BF718A100ED4431 /* DeveloperSettings.swift */,
);
path = Models;
sourceTree = "<group>";
};
3E0196712A392170002648D8 /* ShellIntegration */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1767,6 +1790,7 @@
587B9D8629300ABD00AC7927 /* Views */ = {
isa = PBXGroup;
children = (
30AB4EC12BF7253200ED4431 /* KeyValueTable.swift */,
B62AEDA92A1FCBE5009A9F52 /* AreaTabBar.swift */,
B65B10EB2B073913002852CF /* CEContentUnavailableView.swift */,
B65B10FA2B08B054002852CF /* Divided.swift */,
Expand Down Expand Up @@ -2563,16 +2587,17 @@
B61DA9DD29D929BF00BF4A43 /* Pages */ = {
isa = PBXGroup;
children = (
B664C3AD2B965F4500816B4E /* NavigationSettings */,
B61DA9E129D929F900BF4A43 /* GeneralSettings */,
B6E41C6E29DD15540088F9F4 /* AccountsSettings */,
58F2EAAE292FB2B0004A9BDE /* ThemeSettings */,
B6EA1FF329DA37D3001BF195 /* TextEditingSettings */,
5B698A082B262F8400DE9392 /* SearchSettings */,
30AB4EB72BF7170B00ED4431 /* DeveloperSettings */,
B61DA9E129D929F900BF4A43 /* GeneralSettings */,
B6CF632629E5417C0085880A /* Keybindings */,
B6F0516E29D9E35300D72287 /* LocationsSettings */,
B664C3AD2B965F4500816B4E /* NavigationSettings */,
5B698A082B262F8400DE9392 /* SearchSettings */,
B6F0516D29D9E34200D72287 /* SourceControlSettings */,
B6F0516C29D9E32700D72287 /* TerminalSettings */,
B6EA1FF329DA37D3001BF195 /* TextEditingSettings */,
58F2EAAE292FB2B0004A9BDE /* ThemeSettings */,
);
path = Pages;
sourceTree = "<group>";
Expand Down Expand Up @@ -3459,6 +3484,7 @@
5882252D292C280D00E83CDE /* UtilityAreaSplitTerminalButton.swift in Sources */,
58798238292E30B90085B254 /* FeedbackWindowController.swift in Sources */,
587B9E6C29301D8F00AC7927 /* GitLabNamespace.swift in Sources */,
30AB4EC22BF7253200ED4431 /* KeyValueTable.swift in Sources */,
6C48D8F22972DAFC00D6D205 /* Env+IsFullscreen.swift in Sources */,
587B9E8729301D8F00AC7927 /* GitHubRepositories.swift in Sources */,
6CE6226B2A2A1C730013085C /* UtilityAreaTab.swift in Sources */,
Expand Down Expand Up @@ -3584,6 +3610,7 @@
04BA7C0E2AE2A76E00584E1C /* SourceControlNavigatorChangesCommitView.swift in Sources */,
615AA21A2B0CFD480013FCCC /* LazyStringLoader.swift in Sources */,
6CAAF68A29BC9C2300A1F48A /* (null) in Sources */,
30AB4EBD2BF71CA800ED4431 /* DeveloperSettingsView.swift in Sources */,
6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */,
6CFF967629BEBCD900182D6F /* FileCommands.swift in Sources */,
B60718462B17DC15009CDAB4 /* RepoOutlineGroupItem.swift in Sources */,
Expand All @@ -3606,6 +3633,7 @@
5878DA872918642F00DD95A3 /* AcknowledgementsViewModel.swift in Sources */,
B6E41C7929DE02800088F9F4 /* AccountSelectionView.swift in Sources */,
6CA1AE952B46950000378EAB /* EditorInstance.swift in Sources */,
30AB4EBB2BF718A100ED4431 /* DeveloperSettings.swift in Sources */,
B6C4F2A92B3CB00100B2B140 /* CommitDetailsHeaderView.swift in Sources */,
B6EA1FFB29DB78F6001BF195 /* ThemeSettingsThemeDetails.swift in Sources */,
587B9E7029301D8F00AC7927 /* GitLabUser.swift in Sources */,
Expand Down
172 changes: 172 additions & 0 deletions CodeEdit/Features/CodeEditUI/Views/KeyValueTable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//
// KeyValueTable.swift
// CodeEdit
//
// Created by Abe Malla on 5/16/24.
//

import SwiftUI

struct KeyValueItem: Identifiable, Equatable {
let id = UUID()
let key: String
let value: String
}

private struct NewListTableItemView: View {
@Environment(\.dismiss)
var dismiss

@State private var key = ""
@State private var value = ""

let keyColumnName: String
let valueColumnName: String
let newItemInstruction: String
let headerView: AnyView?
var completion: (String, String) -> Void

init(
_ keyColumnName: String,
_ valueColumnName: String,
_ newItemInstruction: String,
headerView: AnyView? = nil,
completion: @escaping (String, String) -> Void
) {
self.keyColumnName = keyColumnName
self.valueColumnName = valueColumnName
self.newItemInstruction = newItemInstruction
self.headerView = headerView
self.completion = completion
}

var body: some View {
VStack(spacing: 0) {
Form {
Section {
TextField(keyColumnName, text: $key)
.textFieldStyle(.plain)
TextField(valueColumnName, text: $value)
.textFieldStyle(.plain)
} header: {
headerView
}
}
.formStyle(.grouped)
.scrollDisabled(true)
.scrollContentBackground(.hidden)
.onSubmit {
if !key.isEmpty && !value.isEmpty {
completion(key, value)
}
}

HStack {
Spacer()
Button("Cancel") {
dismiss()
}
Button("Add") {
if !key.isEmpty && !value.isEmpty {
completion(key, value)
}
}
.buttonStyle(.borderedProminent)
.disabled(key.isEmpty || value.isEmpty)
}
.padding(.horizontal, 20)
// .padding(.top, 2)
.padding(.bottom, 20)
}
.frame(maxWidth: 480)
}
}

struct KeyValueTable<Header: View>: View {
@Binding var items: [String: String]

let keyColumnName: String
let valueColumnName: String
let newItemInstruction: String
let header: () -> Header

@State private var showingModal = false
@State private var selection: UUID?
@State private var tableItems: [KeyValueItem] = []

init(
items: Binding<[String: String]>,
keyColumnName: String,
valueColumnName: String,
newItemInstruction: String,
@ViewBuilder header: @escaping () -> Header = { EmptyView() }
) {
self._items = items
self.keyColumnName = keyColumnName
self.valueColumnName = valueColumnName
self.newItemInstruction = newItemInstruction
self.header = header
}

var body: some View {
VStack {
Table(tableItems, selection: $selection) {
TableColumn(keyColumnName) { item in
Text(item.key)
}
TableColumn(valueColumnName) { item in
Text(item.value)
}
}
.frame(height: 200)
.actionBar {
HStack(spacing: 2) {
Button {
showingModal = true
} label: {
Image(systemName: "plus")
}

Divider()
.frame(minHeight: 15)

Button {
removeItem()
} label: {
Image(systemName: "minus")
}
.disabled(selection == nil)
.opacity(selection == nil ? 0.5 : 1)
}
Spacer()
}
.sheet(isPresented: $showingModal) {
NewListTableItemView(
keyColumnName,
valueColumnName,
newItemInstruction,
headerView: AnyView(header())
) { key, value in
items[key] = value
updateTableItems()
showingModal = false
}
}
.cornerRadius(6)
.onAppear(perform: updateTableItems)
}
}

private func updateTableItems() {
tableItems = items.map { KeyValueItem(key: $0.key, value: $0.value) }
}

private func removeItem() {
guard let selectedId = selection else { return }
if let selectedItem = tableItems.first(where: { $0.id == selectedId }) {
items.removeValue(forKey: selectedItem.key)
updateTableItems()
}
selection = nil
}
}
10 changes: 9 additions & 1 deletion CodeEdit/Features/Settings/Models/SettingsData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ struct SettingsData: Codable, Hashable {
/// The global settings for keybindings
var keybindings: KeybindingsSettings = .init()

/// Searh Settings
/// Search Settings
var search: SearchSettings = .init()

/// Developer settings for CodeEdit developers
var developerSettings: DeveloperSettings = .init()

/// Default initializer
init() {}

Expand All @@ -71,6 +74,9 @@ struct SettingsData: Codable, Hashable {
KeybindingsSettings.self,
forKey: .keybindings
) ?? .init()
self.developerSettings = try container.decodeIfPresent(
DeveloperSettings.self, forKey: .developerSettings
) ?? .init()
}

// swiftlint:disable cyclomatic_complexity
Expand All @@ -96,6 +102,8 @@ struct SettingsData: Codable, Hashable {
sourceControl.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
case .location:
LocationsSettings().searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
case .developer:
developerSettings.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
case .behavior: return [.init(name, settingName: "Error")]
case .components: return [.init(name, settingName: "Error")]
case .keybindings: return [.init(name, settingName: "Error")]
Expand Down
1 change: 1 addition & 0 deletions CodeEdit/Features/Settings/Models/SettingsPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct SettingsPage: Hashable, Equatable, Identifiable {
case components = "Components"
case location = "Locations"
case advanced = "Advanced"
case developer = "Developer"
}

let id: UUID = UUID()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extension SettingsData {

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

/// The search keys
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// DeveloperSettingsView.swift
// CodeEdit
//
// Created by Abe Malla on 5/16/24.
//

import SwiftUI

/// A view that implements the Developer settings section
struct DeveloperSettingsView: View {
@AppSettings(\.developerSettings.lspBinaries)
var lspBinaries

var body: some View {
SettingsForm {
Section {
KeyValueTable(
items: $lspBinaries,
keyColumnName: "Language",
valueColumnName: "Language Server Path",
newItemInstruction: "Add a language server"
) {
Text("Add a language server")
Text(
"Specify the absolute path to your LSP binary and its associated language."
)
}
} header: {
Text("LSP Binaries")
Text("Specify the language and the absolute path to the language server binary.")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// DeveloperSettings.swift
// CodeEdit
//
// Created by Abe Malla on 5/15/24.
//

import Foundation

extension SettingsData {
struct DeveloperSettings: Codable, Hashable, SearchableSettingsPage {

/// The search keys
var searchKeys: [String] {
[
"Developer",
"Language Server Protocol",
"LSP Binaries"
]
.map { NSLocalizedString($0, comment: "") }
}

/// A dictionary that stores a file type and a path to an LSP binary
var lspBinaries: [String: String] = [:]

/// Default initializer
init() {}

/// Explicit decoder init for setting default values when key is not present in `JSON`
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.lspBinaries = try container.decodeIfPresent(
[String: String].self,
forKey: .lspBinaries
) ?? [:]
}
}
}
Loading

0 comments on commit f2caddf

Please sign in to comment.