PadXcode-iPad/Shared/SharedModels.swift
2026-04-12 22:07:35 -07:00

201 lines
6.5 KiB
Swift

import Foundation
import UIKit
import SwiftUI
// MARK: - File System
struct FileNode: Codable, Identifiable {
var id: String { path }
let name: String
let path: String
let isDirectory: Bool
var children: [FileNode]?
}
// MARK: - Devices
struct ConnectedDevice: Codable, Identifiable {
var id: String { udid }
let udid: String
let name: String
let model: String
let osVersion: String
let isNetworkConnected: Bool
}
// MARK: - Build Wire Types
struct BuildLogMessage: Codable {
enum MessageType: String, Codable { case stdout, stderr, status }
let type: MessageType
let text: String
let exitCode: Int?
}
struct BuildRequest: Codable {
let projectPath: String
let scheme: String
let destination: String
let action: String
let configuration: String
let preBuildHooks: [PreBuildHookRequest]
let bundleIdentifier: String?
let extraBuildSettings: [String]
}
struct PreBuildHookRequest: Codable {
let name: String
let command: String
let workingDirectory: String
let timeoutSeconds: Int
let continueOnFailure: Bool
static func xcodeGen(projectRoot: String) -> PreBuildHookRequest {
PreBuildHookRequest(name: "XcodeGen", command: "./generate.sh",
workingDirectory: projectRoot, timeoutSeconds: 60,
continueOnFailure: false)
}
}
struct BuildSessionResponse: Codable {
let sessionId: String
let webSocketURL: String
}
struct FileContentResponse: Codable {
let path: String
let content: String
}
// MARK: - Console
struct ConsoleLine: Identifiable, Equatable {
let id = UUID()
let text: String
let type: LineType
// Equatable based on content, not identity prevents spurious re-renders
// when identical lines are produced by fromBuildLogMessage.
static func == (lhs: ConsoleLine, rhs: ConsoleLine) -> Bool {
lhs.text == rhs.text && lhs.type == rhs.type
}
enum LineType { case standard, error, warning, success, info }
var color: Color {
switch type {
case .standard: return .primary
case .error: return Color(red: 1.0, green: 0.45, blue: 0.45)
case .warning: return Color(red: 1.0, green: 0.75, blue: 0.35)
case .success: return Color(red: 0.45, green: 0.95, blue: 0.55)
case .info: return .secondary
}
}
static func fromBuildLogMessage(_ msg: BuildLogMessage) -> [ConsoleLine] {
msg.text
.split(separator: "\n", omittingEmptySubsequences: false)
.map { raw in
let text = String(raw)
let type: LineType
if text.contains(": error:") { type = .error }
else if text.contains(": warning:") { type = .warning }
else if msg.type == .stderr { type = .error }
else if msg.type == .status { type = msg.exitCode == 0 ? .success : .error }
else { type = .standard }
return ConsoleLine(text: text, type: type)
}
}
}
// MARK: - Diagnostics
struct DiagnosticRange: Identifiable, Equatable {
enum Severity { case error, warning }
let id = UUID()
let line: Int
let column: Int
let length: Int
let severity: Severity
let message: String
}
// MARK: - LSP / Completions
struct CompletionItem: Identifiable {
let id = UUID()
let label: String
let detail: String
let kind: Int
let insertText: String?
let filterText: String?
var symbolIcon: String {
switch kind {
case 2, 3: return "function"
case 6: return "variable"
case 7: return "cube"
case 8: return "curlybraces"
case 9: return "arrow.triangle.branch"
case 10: return "square.split.bottomrightquarter"
case 14: return "key.fill"
case 15: return "chevron.left.forwardslash.chevron.right"
default: return "doc.text"
}
}
}
struct DefinitionLocation {
let filePath: String
let line: Int
let column: Int
}
// MARK: - Notification Names
extension Notification.Name {
static let gitCheckoutCompleted = Notification.Name("git.checkout.completed")
static let gitCommitCompleted = Notification.Name("git.commit.completed")
static let gitPushCompleted = Notification.Name("git.push.completed")
static let gitSyncCompleted = Notification.Name("git.sync.completed")
static let gitMergeCompleted = Notification.Name("git.merge.completed")
static let gitWriteCompleted = Notification.Name("git.write.completed")
static let gitReadCompleted = Notification.Name("git.read.completed")
static let gitOperationFailed = Notification.Name("git.operation.failed")
static let consoleClearRequested = Notification.Name("console.clear.requested")
static let navigateToRange = Notification.Name("editor.navigate.range")
static let triggerCompletion = Notification.Name("editor.trigger.completion")
static let openSettings = Notification.Name("padxcode.open.settings")
static let editorFindNext = Notification.Name("editor.find.next")
static let editorFindPrev = Notification.Name("editor.find.prev")
static let editorFontSizeChange = Notification.Name("editor.font.size.change")
}
// MARK: - String helpers
extension String {
var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? self
}
func replacingFirstOccurrence(of target: String, with replacement: String) -> String {
guard let range = self.range(of: target) else { return self }
return self.replacingCharacters(in: range, with: replacement)
}
}
// MARK: - UIColor hex convenience (used by themes and editor views)
extension UIColor {
convenience init(hex: String) {
var h = hex.trimmingCharacters(in: .whitespacesAndNewlines)
h = h.hasPrefix("#") ? String(h.dropFirst()) : h
var rgb: UInt64 = 0
Scanner(string: h).scanHexInt64(&rgb)
self.init(
red: CGFloat((rgb >> 16) & 0xFF) / 255.0,
green: CGFloat((rgb >> 8) & 0xFF) / 255.0,
blue: CGFloat( rgb & 0xFF) / 255.0,
alpha: 1.0
)
}
}