Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// ProjectCEWorkspaceSettings.swift
// CodeEdit
//
// Created by Axel Martinez on 27/3/24.
//

import SwiftUI

extension CEWorkspaceSettingsData {
/// The project setting
Comment thread
armartinez marked this conversation as resolved.
Outdated
struct ProjectSettings: Codable, Hashable, SearchableSettingsPage {
var searchKeys: [String] {
[
"Project Name",
]
.map { NSLocalizedString($0, comment: "") }
}

/// The project name
Comment thread
armartinez marked this conversation as resolved.
Outdated
var projectName: 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.projectName = try container.decodeIfPresent(
String.self,
forKey: .projectName
) ?? ""
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// CEWorkspaceSettingsData+TasksSettings.swift
// CodeEdit
//
// Created by Axel Martinez on 27/3/24.
//

import Foundation
import Collections

extension CEWorkspaceSettingsData {
/// The tasks setting
struct TasksSettings: Codable, Hashable, SearchableSettingsPage {
var items: [CETask] = []

var searchKeys: [String] {
[
"Tasks"
]
.map { NSLocalizedString($0, comment: "") }
}

/// The tasks behavior of the app
var enabled: Bool = true

/// 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.items = try container.decodeIfPresent(
[CETask].self,
forKey: .items
) ?? []
Comment thread
armartinez marked this conversation as resolved.
Outdated
self.enabled = try container.decodeIfPresent(
Bool.self,
forKey: .enabled
) ?? true
}
}
}
71 changes: 71 additions & 0 deletions CodeEdit/Features/CEWorkspace/Models/CETask.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// CETask.swift
// CodeEdit
//
// Created by Axel Martinez on 2/4/24.
//

import SwiftUI

/// Represents a CodeEdit task that will be executed by the a
/// task manager.
Comment thread
armartinez marked this conversation as resolved.
Outdated
struct CETask: Identifiable, Hashable, Codable {
var id = UUID()
var name: String = ""
var target: String = ""
var workingDirectory: String = ""
var command: String = ""
var env: [EnvironmentVariable] = []
Comment thread
armartinez marked this conversation as resolved.
Outdated

var isInvalid: Bool {
name.isEmpty ||
command.isEmpty ||
target.isEmpty ||
workingDirectory.isEmpty
}

enum CodingKeys: String, CodingKey {
case name
case target
case workingDirectory
case command
case env
}

struct EnvironmentVariable: Identifiable, Hashable, Codable {
var id = UUID()
var name: String = ""
var value: String = ""

private struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
Comment thread
armartinez marked this conversation as resolved.

init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}

init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
}

init() {
}
Comment thread
armartinez marked this conversation as resolved.
Outdated

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
for key in container.allKeys {
name = key.stringValue
value = try container.decode(String.self, forKey: key)
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(value, forKey: CodingKeys(stringValue: name)!)
}
}
}
78 changes: 78 additions & 0 deletions CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// CEWorkspaceSettings.swift
// CodeEdit
//
// Created by Axel Martinez on 27/3/24.
//

import Foundation
Comment thread
armartinez marked this conversation as resolved.
Outdated
import SwiftUI
import Combine

/// The CodeEdit workspace settings model.
final class CEWorkspaceSettings: ObservableObject {
@ObservedObject private var workspace: WorkspaceDocument
@Published public var preferences: CEWorkspaceSettingsData = .init()

private var savedSettings = false
private var storeTask: AnyCancellable!
private let filemanager = FileManager.default
Comment thread
armartinez marked this conversation as resolved.
Outdated

private var folderURL: URL? {
guard let workspaceURL = workspace.fileURL else {
return nil
}

return workspaceURL
.appendingPathComponent(".codeedit", isDirectory: true)
}

private var settingsURL: URL? {
folderURL?
.appendingPathComponent("settings")
.appendingPathExtension("json")
}

init(workspaceDocument: WorkspaceDocument) {
self.workspace = workspaceDocument

loadSettings()

self.storeTask = self.$preferences.throttle(for: 2, scheduler: RunLoop.main, latest: true).sink {
if !self.savedSettings, let folderURL = self.folderURL {
try? self.filemanager.createDirectory(at: folderURL, withIntermediateDirectories: false)
self.savedSettings = true
}

try? self.savePreferences($0)
}
}

/// Load and construct ``Settings`` model from
/// `.codeedit/settings.json`
Comment thread
armartinez marked this conversation as resolved.
Outdated
private func loadSettings() {
if let settingsURL = settingsURL {
if filemanager.fileExists(atPath: settingsURL.path) {
guard let json = try? Data(contentsOf: settingsURL),
let prefs = try? JSONDecoder().decode(CEWorkspaceSettingsData.self, from: json)
else {
return
}
self.savedSettings = true
self.preferences = prefs
}
}
return
}

/// Save``Settings`` model to
/// `.codeedit/settings.json`
Comment thread
armartinez marked this conversation as resolved.
Outdated
private func savePreferences(_ data: CEWorkspaceSettingsData) throws {
guard let settingsURL = settingsURL else { return }
Comment thread
armartinez marked this conversation as resolved.

let data = try JSONEncoder().encode(data)
let json = try JSONSerialization.jsonObject(with: data)
let prettyJSON = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted])
try prettyJSON.write(to: settingsURL, options: .atomic)
}
}
50 changes: 50 additions & 0 deletions CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// CEWorkspaceSettingsData.swift
// CodeEdit
//
// Created by Axel Martinez on 27/3/24.
//

import SwiftUI
import Foundation

/// # Workspace Settings
Comment thread
armartinez marked this conversation as resolved.
///
/// The model structure of the workspace settings for `CodeEdit`
Comment thread
armartinez marked this conversation as resolved.
Outdated
///
/// A `JSON` representation is persisted in the workspace's `./codeedit/settings.json`. file
///
/// - Note: Make sure to implement the ``init(from:)`` initializer, decoding
/// all properties with
/// [`decodeIfPresent`](https://developer.apple.com/documentation/swift/keyeddecodingcontainer/2921389-decodeifpresent)
/// and providing a default value. Otherwise all settings get overridden.
Comment thread
armartinez marked this conversation as resolved.
Outdated
struct CEWorkspaceSettingsData: Codable, Hashable {
/// The project global settings
var project: ProjectSettings = .init()

/// The tasks settings
var tasks: TasksSettings = .init()

/// 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.project = try container.decodeIfPresent(ProjectSettings.self, forKey: .project) ?? .init()
self.tasks = try container.decodeIfPresent(TasksSettings.self, forKey: .tasks) ?? .init()
}

func propertiesOf(_ name: CEWorkspaceSettingsPage.Name) -> [CEWorkspaceSettingsPage] {
var settings: [CEWorkspaceSettingsPage] = []

switch name {
case .project:
project.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
case .tasks:
tasks.searchKeys.forEach { settings.append(.init(name, isSetting: true, settingName: $0)) }
}

return settings
}
}
51 changes: 51 additions & 0 deletions CodeEdit/Features/CEWorkspace/Models/CEWorkspaceSettingsPage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// CEWorkspaceSettingsPage.swift
// CodeEdit
//
// Created by Axel Martinez on 27/3/24.
//

import Foundation
import SwiftUI

/// A struct for a workspace settings page
Comment thread
armartinez marked this conversation as resolved.
Outdated
struct CEWorkspaceSettingsPage: Hashable, Equatable, Identifiable {
/// A struct for a sidebar icon, with a base color and SF Symbol
enum IconResource: Equatable, Hashable {
case system(_ name: String)
case symbol(_ name: String)
case asset(_ name: String)
}

/// An enum of all the settings pages
enum Name: String {
case project = "Project"
case tasks = "Tasks"
}

let id: UUID = UUID()

let name: Name
let baseColor: Color?
let isSetting: Bool
let settingName: String
var nameString: LocalizedStringKey {
LocalizedStringKey(name.rawValue)
}
let icon: IconResource?

/// Default initializer
init(
_ name: Name,
baseColor: Color? = nil,
icon: IconResource? = nil,
isSetting: Bool = false,
settingName: String = ""
) {
self.name = name
self.baseColor = baseColor
self.icon = icon
self.isSetting = isSetting
self.settingName = settingName
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// CEWorkpaceSettingsSearchResult.swift
// CodeEdit
//
// Created by Axel Martinez on 27/3/24.
//

import Foundation
import SwiftUI

// TODO: Extend this struct further to support setting "flashing"
class CEWorkspaceSettingsSearchResult: Identifiable {
Comment thread
armartinez marked this conversation as resolved.
Outdated
init(
pageFound: Bool,
pages: [CEWorkspaceSettingsPage]
) {
self.pageFound = pageFound
self.pages = pages
}

let id: UUID = UUID()

let pageFound: Bool
let pages: [CEWorkspaceSettingsPage]
Comment thread
armartinez marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// PageAndCEWorkspaceSettings.swift
// CodeEdit
//
// Created by Axel Martinez on 27/3/24.
//

import Foundation

struct PageAndCEWorkspaceSettings: Identifiable, Equatable {
let id: UUID = UUID()
let page: CEWorkspaceSettingsPage
let settings: [CEWorkspaceSettingsPage]

init(_ page: CEWorkspaceSettingsPage) {
self.page = page
self.settings = CEWorkspaceSettingsData().propertiesOf(page.name)
}
}
Loading