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 ) } }