Skip to content

Commit 139bc53

Browse files
Merge branch 'main' into dep-injection
2 parents d78cc2a + 58faf4d commit 139bc53

28 files changed

+996
-10
lines changed

.all-contributorsrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,8 @@
647647
"avatar_url": "https://avatars.githubusercontent.com/u/1909987?v=4",
648648
"profile": "https://github.com/armartinez",
649649
"contributions": [
650-
"bug"
650+
"bug",
651+
"code"
651652
]
652653
},
653654
{

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 100 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// ProjectCEWorkspaceSettings.swift
3+
// CodeEdit
4+
//
5+
// Created by Axel Martinez on 27/3/24.
6+
//
7+
8+
import SwiftUI
9+
10+
extension CEWorkspaceSettingsData {
11+
/// Workspace settings for the project tab.
12+
struct ProjectSettings: Codable, Hashable, SearchableSettingsPage {
13+
var searchKeys: [String] {
14+
[
15+
"Project Name",
16+
]
17+
.map { NSLocalizedString($0, comment: "") }
18+
}
19+
20+
var projectName: String = ""
21+
22+
init() {}
23+
24+
/// Explicit decoder init for setting default values when key is not present in `JSON`
25+
init(from decoder: Decoder) throws {
26+
let container = try decoder.container(keyedBy: CodingKeys.self)
27+
self.projectName = try container.decodeIfPresent(String.self, forKey: .projectName) ?? ""
28+
}
29+
}
30+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// CEWorkspaceSettingsData+TasksSettings.swift
3+
// CodeEdit
4+
//
5+
// Created by Axel Martinez on 27/3/24.
6+
//
7+
8+
import Foundation
9+
import Collections
10+
11+
extension CEWorkspaceSettingsData {
12+
/// Workspace settings for the tasks tab.
13+
struct TasksSettings: Codable, Hashable, SearchableSettingsPage {
14+
var items: [CETask] = []
15+
16+
var searchKeys: [String] {
17+
[
18+
"Tasks"
19+
]
20+
.map { NSLocalizedString($0, comment: "") }
21+
}
22+
23+
/// The tasks functionality behavior of the app
24+
var enabled: Bool = true
25+
26+
init() {}
27+
28+
/// Explicit decoder init for setting default values when key is not present in `JSON`
29+
init(from decoder: Decoder) throws {
30+
let container = try decoder.container(keyedBy: CodingKeys.self)
31+
self.items = try container.decodeIfPresent([CETask].self, forKey: .items) ?? []
32+
self.enabled = try container.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
33+
}
34+
}
35+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// CETask.swift
3+
// CodeEdit
4+
//
5+
// Created by Axel Martinez on 2/4/24.
6+
//
7+
8+
import SwiftUI
9+
10+
/// CodeEdit task that will be executed by the task manager.
11+
struct CETask: Identifiable, Hashable, Codable {
12+
var id = UUID()
13+
var name: String = ""
14+
var target: String = ""
15+
var workingDirectory: String = ""
16+
var command: String = ""
17+
var environmentVariables: [EnvironmentVariable] = []
18+
19+
var isInvalid: Bool {
20+
name.isEmpty ||
21+
command.isEmpty ||
22+
target.isEmpty ||
23+
workingDirectory.isEmpty
24+
}
25+
26+
enum CodingKeys: String, CodingKey {
27+
case name
28+
case target
29+
case workingDirectory
30+
case command
31+
case environmentVariables
32+
}
33+
34+
struct EnvironmentVariable: Identifiable, Hashable, Codable {
35+
var id = UUID()
36+
var name: String = ""
37+
var value: String = ""
38+
39+
/// Enables encoding the environment variables as a `name`:`value`pair.
40+
private struct CodingKeys: CodingKey {
41+
var stringValue: String
42+
var intValue: Int?
43+
44+
init?(stringValue: String) {
45+
self.stringValue = stringValue
46+
self.intValue = nil
47+
}
48+
49+
/// Required by the CodingKey protocol but not being currently used.
50+
init?(intValue: Int) {
51+
self.stringValue = "\(intValue)"
52+
self.intValue = intValue
53+
}
54+
}
55+
56+
init() {}
57+
58+
init(from decoder: Decoder) throws {
59+
let container = try decoder.container(keyedBy: CodingKeys.self)
60+
for key in container.allKeys {
61+
name = key.stringValue
62+
value = try container.decode(String.self, forKey: key)
63+
}
64+
}
65+
66+
public func encode(to encoder: Encoder) throws {
67+
var container = encoder.container(keyedBy: CodingKeys.self)
68+
try container.encode(value, forKey: CodingKeys(stringValue: name)!)
69+
}
70+
}
71+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// CEWorkspaceSettings.swift
3+
// CodeEdit
4+
//
5+
// Created by Axel Martinez on 27/3/24.
6+
//
7+
8+
import SwiftUI
9+
import Combine
10+
11+
/// The CodeEdit workspace settings model.
12+
final class CEWorkspaceSettings: ObservableObject {
13+
@ObservedObject private var workspace: WorkspaceDocument
14+
@Published public var preferences: CEWorkspaceSettingsData = .init()
15+
16+
private var savedSettings = false
17+
private var storeTask: AnyCancellable!
18+
private let fileManager = FileManager.default
19+
20+
private var folderURL: URL? {
21+
guard let workspaceURL = workspace.fileURL else {
22+
return nil
23+
}
24+
25+
return workspaceURL
26+
.appendingPathComponent(".codeedit", isDirectory: true)
27+
}
28+
29+
private var settingsURL: URL? {
30+
folderURL?
31+
.appendingPathComponent("settings")
32+
.appendingPathExtension("json")
33+
}
34+
35+
init(workspaceDocument: WorkspaceDocument) {
36+
self.workspace = workspaceDocument
37+
38+
loadSettings()
39+
40+
self.storeTask = self.$preferences.throttle(for: 2, scheduler: RunLoop.main, latest: true).sink {
41+
if !self.savedSettings, let folderURL = self.folderURL {
42+
try? self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: false)
43+
self.savedSettings = true
44+
}
45+
46+
try? self.savePreferences($0)
47+
}
48+
}
49+
50+
/// Load and construct ``CEWorkspaceSettings`` model from `.codeedit/settings.json`
51+
private func loadSettings() {
52+
if let settingsURL = settingsURL {
53+
if fileManager.fileExists(atPath: settingsURL.path) {
54+
guard let json = try? Data(contentsOf: settingsURL),
55+
let prefs = try? JSONDecoder().decode(CEWorkspaceSettingsData.self, from: json)
56+
else { return }
57+
58+
self.savedSettings = true
59+
self.preferences = prefs
60+
}
61+
}
62+
}
63+
64+
/// Save``CEWorkspaceSettings`` model to `.codeedit/settings.json`
65+
private func savePreferences(_ data: CEWorkspaceSettingsData) throws {
66+
guard let settingsURL = settingsURL else { return }
67+
68+
let data = try JSONEncoder().encode(data)
69+
let json = try JSONSerialization.jsonObject(with: data)
70+
let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted])
71+
try prettyJSON.write(to: settingsURL, options: .atomic)
72+
}
73+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// CEWorkspaceSettingsData.swift
3+
// CodeEdit
4+
//
5+
// Created by Axel Martinez on 27/3/24.
6+
//
7+
8+
import SwiftUI
9+
import Foundation
10+
11+
/// # Workspace Settings
12+
///
13+
/// The model of the workspace settings for `CodeEdit` that control the behavior of some functionality at the workspace
14+
/// level like the workspace name or defining tasks. A `JSON` representation is persisted in the workspace's
15+
/// `./codeedit/settings.json`. file
16+
struct CEWorkspaceSettingsData: Codable, Hashable {
17+
/// The project global settings
18+
var project: ProjectSettings = .init()
19+
20+
/// The tasks settings
21+
var tasks: TasksSettings = .init()
22+
23+
init() {}
24+
25+
/// Explicit decoder init for setting default values when key is not present in `JSON`
26+
init(from decoder: Decoder) throws {
27+
let container = try decoder.container(keyedBy: CodingKeys.self)
28+
self.project = try container.decodeIfPresent(ProjectSettings.self, forKey: .project) ?? .init()
29+
self.tasks = try container.decodeIfPresent(TasksSettings.self, forKey: .tasks) ?? .init()
30+
}
31+
32+
func propertiesOf(_ name: CEWorkspaceSettingsPage.Name) -> [CEWorkspaceSettingsPage] {
33+
var settings: [CEWorkspaceSettingsPage] = []
34+
35+
switch name {
36+
case .project:
37+
project.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
38+
case .tasks:
39+
tasks.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
40+
}
41+
42+
return settings
43+
}
44+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// CEWorkspaceSettingsPage.swift
3+
// CodeEdit
4+
//
5+
// Created by Axel Martinez on 27/3/24.
6+
//
7+
8+
import Foundation
9+
import SwiftUI
10+
11+
/// Represents a workspace settings tab.
12+
struct CEWorkspaceSettingsPage: Hashable, Equatable, Identifiable {
13+
/// Sidebar icon, with a base color and SF Symbol
14+
enum IconResource: Equatable, Hashable {
15+
case system(_ name: String)
16+
case symbol(_ name: String)
17+
case asset(_ name: String)
18+
}
19+
20+
/// All the workspace settings pages
21+
enum Name: String {
22+
case project = "Project"
23+
case tasks = "Tasks"
24+
}
25+
26+
let id: UUID = UUID()
27+
28+
let name: Name
29+
let baseColor: Color?
30+
let isSetting: Bool
31+
let settingName: String
32+
var nameString: LocalizedStringKey {
33+
LocalizedStringKey(name.rawValue)
34+
}
35+
let icon: IconResource?
36+
37+
init(
38+
_ name: Name,
39+
baseColor: Color? = nil,
40+
icon: IconResource? = nil,
41+
isSetting: Bool = false,
42+
settingName: String = ""
43+
) {
44+
self.name = name
45+
self.baseColor = baseColor
46+
self.icon = icon
47+
self.isSetting = isSetting
48+
self.settingName = settingName
49+
}
50+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// CEWorkpaceSettingsSearchResult.swift
3+
// CodeEdit
4+
//
5+
// Created by Axel Martinez on 27/3/24.
6+
//
7+
8+
import SwiftUI
9+
10+
// TODO: Extend this struct further to support setting "flashing"
11+
final class CEWorkspaceSettingsSearchResult: Identifiable {
12+
let id: UUID = UUID()
13+
let pageFound: Bool
14+
let pages: [CEWorkspaceSettingsPage]
15+
16+
init(
17+
pageFound: Bool,
18+
pages: [CEWorkspaceSettingsPage]
19+
) {
20+
self.pageFound = pageFound
21+
self.pages = pages
22+
}
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// PageAndCEWorkspaceSettings.swift
3+
// CodeEdit
4+
//
5+
// Created by Axel Martinez on 27/3/24.
6+
//
7+
8+
import Foundation
9+
10+
struct PageAndCEWorkspaceSettings: Identifiable, Equatable {
11+
let id: UUID = UUID()
12+
let page: CEWorkspaceSettingsPage
13+
let settings: [CEWorkspaceSettingsPage]
14+
15+
init(_ page: CEWorkspaceSettingsPage) {
16+
self.page = page
17+
self.settings = CEWorkspaceSettingsData().propertiesOf(page.name)
18+
}
19+
}

0 commit comments

Comments
 (0)