Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
35 changes: 15 additions & 20 deletions Sources/CodeCanvas/CodeCanvas.swift
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import Foundation

#if canImport(SwiftUI)
import SwiftUI
#endif

/// Public entry-points for the CodeCanvas module.
public enum CodeCanvasAPI {
/// Format the document text (placeholder using SwiftFormat later).
public static func format(_ doc: CodeDocument) -> CodeDocument {
// TODO: integrate real formatting via swift-format when needed.
return doc
public struct CodeCanvas: View {
private let extensions: [CodeCanvasExtension]

public init(extensions: [CodeCanvasExtension]) {
self.extensions = extensions
}

public var body: some View {
CodeCanvasShell(extensions: extensions)
}
}

#if canImport(SwiftUI)
/// A minimal SwiftUI-based editor view for Apple platforms.
public struct CodeCanvasView: View {
@State private var text: String
#else

public init(initialText: String = "// Welcome to CodeCanvas\n") {
self._text = State(initialValue: initialText)
}
public struct CodeCanvas {
public init() {}

public var body: some View {
TextEditor(text: $text)
.font(.system(.body, design: .monospaced))
.padding()
public func start() {
CodeCanvasTUI().start()
}
}

#endif
7 changes: 7 additions & 0 deletions Sources/CodeCanvas/Console/CodeCanvasTUI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

public struct CodeCanvasTUI {
public func start() {

}
}
10 changes: 0 additions & 10 deletions Sources/CodeCanvas/Document.swift

This file was deleted.

27 changes: 27 additions & 0 deletions Sources/CodeCanvas/Models/CodeCanvasBench.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#if canImport(SwiftUI)
import Foundation
import SwiftUI

public protocol CodeCanvasBenchAction {
var id: String { get }
var title: String { get }
var icon: String { get }
func perform(on bench: CodeCanvasBench)
}

public protocol CodeCanvasBenchComponent{
var id: String { get }
var title: String { get }
var icon: String { get }
var actions: [CodeCanvasBenchAction] { get }
func content() -> AnyView
}

public protocol CodeCanvasBench {
var id: String { get }
var name: String { get }
var icon: String { get }
var components: [CodeCanvasBenchComponent] { get }
}

#endif
13 changes: 13 additions & 0 deletions Sources/CodeCanvas/Models/CodeCanvasExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#if canImport(SwiftUI)
public struct CodeCanvasExtension {
public let name: String
public let icon: String
public let benches: [CodeCanvasBench]

public init(name: String, icon: String, benches: [CodeCanvasBench]) {
self.name = name
self.icon = icon
self.benches = benches
}
}
#endif
11 changes: 11 additions & 0 deletions Sources/CodeCanvas/Models/CodeCanvasFile.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public struct CodeCanvasFile: Identifiable {
public let id: String
public let name: String
public let content: String

public init(id: String, name: String, content: String) {
self.id = id
self.name = name
self.content = content
}
}
7 changes: 7 additions & 0 deletions Sources/CodeCanvas/Models/CodeCanvasWorkspace.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#if canImport(SwiftUI)
import SwiftUI

public protocol CodeCanvasWorkspace: Identifiable {
func content() -> AnyView
}
#endif
17 changes: 17 additions & 0 deletions Sources/CodeCanvas/State/CodeCanvasStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#if canImport(SwiftUI)
import SwiftUI

public final class CodeCanvasStore: ObservableObject {
@Published public var workspace: (any CodeCanvasWorkspace)?

Copilot AI Aug 9, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workspace property should be private(set) to prevent external modification and maintain encapsulation. External code should only be able to modify the workspace through the open() and clear() methods.

Suggested change
@Published public var workspace: (any CodeCanvasWorkspace)?
@Published public private(set) var workspace: (any CodeCanvasWorkspace)?

Copilot uses AI. Check for mistakes.

public init() {}

public func open(_ workspace: any CodeCanvasWorkspace) {
self.workspace = workspace
}

public func clear() {
workspace = nil
}
}
#endif
13 changes: 0 additions & 13 deletions Sources/CodeCanvas/TUI.swift

This file was deleted.

41 changes: 41 additions & 0 deletions Sources/CodeCanvas/Views/CodeCanvasShell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#if canImport(SwiftUI)
import SwiftUI

public struct CodeCanvasShell: View {
private let extensions: [CodeCanvasExtension]

@State private var showInspector = false
@StateObject private var store = CodeCanvasStore()

public init(extensions: [CodeCanvasExtension]) {
self.extensions = extensions
}

public var body: some View {
NavigationSplitView {
CodeBenchContainer(benches: extensions.flatMap { $0.benches })
} detail: {
CodeSpaceContainer()
}
.environmentObject(store)
.inspector(
isPresented: $showInspector,
content: {
CodeInspectorContainer()
#if os(macOS)
.inspectorColumnWidth(min: 340, ideal: 340, max: 680)
#endif
.toolbar {
Spacer()
Button(action: {
showInspector.toggle()
}) {
Label("Toggle Inspector", systemImage: "sidebar.right")
}
}
}
)
}
}

#endif
53 changes: 53 additions & 0 deletions Sources/CodeCanvas/Views/Contents/CodeBenchContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#if canImport(SwiftUI)
import SwiftUI

public struct CodeBenchContainer: View {
private let benches: [CodeCanvasBench]
@State private var selectedBench: (any CodeCanvasBench)?
@EnvironmentObject private var store: CodeCanvasStore

public init(benches: [CodeCanvasBench]) {
self.benches = benches
_selectedBench = State(initialValue: benches.first)
}

public var body: some View {
HStack(spacing: 0) {
CodeBenchSelector(benches: benches) { bench in
selectedBench = bench
store.clear()
}
.frame(maxHeight: .infinity)

if let selectedBench {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
ForEach(selectedBench.components, id: \.id) { component in
DisclosureGroup {
component.content()
} label: {
HStack {
Text(component.title).font(.headline)
Spacer()
ForEach(component.actions, id: \.id) { action in
Button {
action.perform(on: selectedBench)
} label: {
Image(systemName: action.icon)
}
.buttonStyle(.plain)
.help(action.title)
}
}
}
}
}
}
} else {
Spacer()
}
}
}
}

#endif
15 changes: 15 additions & 0 deletions Sources/CodeCanvas/Views/Contents/CodeInspectorContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#if canImport(SwiftUI)
import SwiftUI

public struct CodeInspectorContainer: View {
public init() {}

public var body: some View {
VStack {
Spacer()
Text("Inspector")
Spacer()
}
}
}
#endif
17 changes: 17 additions & 0 deletions Sources/CodeCanvas/Views/Contents/CodeSpaceContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#if canImport(SwiftUI)
import SwiftUI

public struct CodeSpaceContainer: View {
@EnvironmentObject private var store: CodeCanvasStore

public init() {}

public var body: some View {
if let workspace = store.workspace {
workspace.content()
} else {
Text("Canvas")
}
}
}
#endif
43 changes: 43 additions & 0 deletions Sources/CodeCanvas/Views/Controls/CodeBenchSelector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#if canImport(SwiftUI)
import SwiftUI

public struct CodeBenchSelector: View {
private let benches: [CodeCanvasBench]
private let onSelect: (CodeCanvasBench) -> Void

@State private var selectedID: String?

public init(benches: [CodeCanvasBench], onSelect: @escaping (CodeCanvasBench) -> Void) {
self.benches = benches
self.onSelect = onSelect
self._selectedID = State(initialValue: benches.first?.id)
}

public var body: some View {
VStack(spacing: 15) {
ForEach(benches, id: \.id) { bench in
Button(action: {
selectedID = bench.id
onSelect(bench)
}) {
Image(systemName: bench.icon)
.font(.title)
.symbolVariant(selectedID == bench.id ? .fill : .none)
.frame(width: 50, height: 50)
.foregroundColor(selectedID == bench.id ? .accentColor : .secondary)
.background(
Color.secondary.opacity(selectedID == bench.id ? 0.25 : 0)
)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
.buttonStyle(.plain)
.help(bench.name)
}
Spacer()
}
.padding(.vertical)
.frame(width: 60)
}
}

#endif
46 changes: 32 additions & 14 deletions Sources/CodeCanvasShowCase/CodeCanvasShowCase.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
import Foundation
import CodeCanvas
import Foundation

#if canImport(SwiftUI)
import SwiftUI
import SwiftUI

@main
struct CodeCanvasShowCaseApp: App {
@main
struct CodeCanvasShowCaseApp: App {
#if os(macOS)
// Ensure the app shows in Dock, has menu bar, and supports full screen when launched as a SwiftPM executable
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#endif
var body: some Scene {
WindowGroup {
CodeCanvasView(initialText: "// CodeCanvas Showcase\nprint(\"Hello, world!\")\n")
}
WindowGroup {
CodeCanvas(
extensions: [
CodeCanvasExtension(name: "Editor", icon: "code", benches: [CodeEditorBench()]),
CodeCanvasExtension(name: "Restful", icon: "network", benches: [ClientRestfulBench()]),
]
)
}
}
}
#if os(macOS)
import AppKit
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// Switch activation policy to regular so we have Dock icon and menu bar
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
}
}
}
#endif
#else
@main
struct CodeCanvasShowCaseCLI {
@main
struct CodeCanvasShowCaseCLI {
static func main() {
let doc = CodeDocument(text: "// CodeCanvas Showcase CLI\nprint(\"Hello, world!\")\n")
let tui = CodeCanvasTUI()
tui.start(with: doc)
let tui = CodeCanvas()
tui.start()
}
}
}
#endif
Loading