├── .gitattributes
├── .github
├── CodeEditKit-Icon-128@2x.png
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ └── feature_request.yml
├── pull_request_template.md
├── scripts
│ ├── build-docc.sh
│ └── tests.sh
└── workflows
│ ├── CI-pull-request.yml
│ ├── CI-push.yml
│ ├── add-to-project.yml
│ ├── build-documentation.yml
│ ├── swiftlint.yml
│ └── tests.yml
├── .gitignore
├── .swiftlint.yml
├── LICENSE.md
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── CodeEditKit
│ ├── CodableColorArray.swift
│ ├── CodableWrappers.swift
│ ├── CodeEditExtension+Body.swift
│ ├── CodeEditExtension.swift
│ ├── Debugging
│ └── Logging.swift
│ ├── Documentation.docc
│ ├── CodeEditExtension.md
│ ├── Commands.md
│ ├── CreatingProject.md
│ ├── Documentation.md
│ ├── GenericExtension.md
│ ├── LanguageServers.md
│ ├── Settings.md
│ ├── Sidebars.md
│ ├── Snippets.md
│ ├── Themes.md
│ └── ToolbarItems.md
│ ├── Entitlements.swift
│ ├── ExtensionKind.swift
│ ├── GenericScene.swift
│ ├── UIExtensions
│ ├── Environment
│ │ ├── CEEnvironment.swift
│ │ ├── CEEnvironmentKey.swift
│ │ ├── CEEnvironmentModifier.swift
│ │ ├── CEOpenWindowEnvKey.swift
│ │ └── EnvironmentObjcPublisher.swift
│ ├── Settings
│ │ ├── GeneralSettingsScene.swift
│ │ ├── GeneralSettingsView.swift
│ │ └── SettingsExtension.swift
│ └── Sidebar
│ │ ├── Inspector.swift
│ │ ├── Navigator.swift
│ │ ├── ResolvedSidebar.swift
│ │ ├── Sidebar+Help.swift
│ │ ├── Sidebar.swift
│ │ ├── SidebarBuilder.swift
│ │ ├── SidebarExtension.swift
│ │ ├── SidebarNever.swift
│ │ └── TupleSidebar.swift
│ └── XPCWrapper.swift
└── Tests
└── CodeEditKitTests
└── CEExtensionKitTests.swift
/.gitattributes:
--------------------------------------------------------------------------------
1 | .github/** linguist-vendored
2 | Sources/CodeEditTextView/Documentation.docc/** linguist-documentation
3 |
--------------------------------------------------------------------------------
/.github/CodeEditKit-Icon-128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeEditApp/CodeEditKit/ad28213a968586abb0cb21a8a56a3587227895f1/.github/CodeEditKit-Icon-128@2x.png
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: 🐞 Bug report
2 | description: Something is not working as expected.
3 | title: 🐞
4 | labels: bug
5 |
6 | body:
7 | - type: textarea
8 | attributes:
9 | label: Description
10 | placeholder: >-
11 | A clear and concise description of what the bug is...
12 | validations:
13 | required: true
14 |
15 | - type: textarea
16 | attributes:
17 | label: To Reproduce
18 | description: >-
19 | Steps to reliably reproduce the behavior.
20 | placeholder: |
21 | 1. Go to '...'
22 | 2. Click on '....'
23 | 3. Scroll down to '....'
24 | 4. See error
25 | validations:
26 | required: true
27 |
28 | - type: textarea
29 | attributes:
30 | label: Expected Behavior
31 | placeholder: >-
32 | A clear and concise description of what you expected to happen...
33 | validations:
34 | required: true
35 |
36 | - type: textarea
37 | attributes:
38 | label: Version Information
39 | description: >-
40 | click on the version number on the welcome screen
41 | value: |
42 | CodeEditKit: [e.g. 0.0.x-alpha.y]
43 | macOS: [e.g. 13.2.1]
44 | Xcode: [e.g. 14.2]
45 |
46 | - type: textarea
47 | attributes:
48 | label: Additional Context
49 | placeholder: >-
50 | Any other context or considerations about the bug...
51 |
52 | - type: textarea
53 | attributes:
54 | label: Screenshots
55 | placeholder: >-
56 | If applicable, please provide relevant screenshots or screen recordings...
57 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: ✨ Feature request
2 | description: Suggest an idea for this project
3 | title: ✨
4 | labels: enhancement
5 |
6 | body:
7 | - type: textarea
8 | attributes:
9 | label: Description
10 | placeholder: >-
11 | A clear and concise description of what you would like to happen...
12 | validations:
13 | required: true
14 |
15 | - type: textarea
16 | attributes:
17 | label: Alternatives Considered
18 | placeholder: >-
19 | Any alternative solutions or features you've considered...
20 |
21 | - type: textarea
22 | attributes:
23 | label: Additional Context
24 | placeholder: >-
25 | Any other context or considerations about the feature request...
26 |
27 | - type: textarea
28 | attributes:
29 | label: Screenshots
30 | placeholder: >-
31 | If applicable, please provide relevant screenshots or screen recordings...
32 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Description
4 |
5 |
6 |
7 | ### Related Issues
8 |
9 |
10 |
11 |
12 |
13 | * #ISSUE_NUMBER
14 |
15 | ### Checklist
16 |
17 |
18 |
19 | - [ ] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)
20 | - [ ] The issues this PR addresses are related to each other
21 | - [ ] My changes generate no new warnings
22 | - [ ] My code builds and runs on my machine
23 | - [ ] My changes are all related to the related issue above
24 | - [ ] I documented my code
25 |
26 | ### Screenshots
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/.github/scripts/build-docc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export LC_CTYPE=en_US.UTF-8
4 |
5 | set -o pipefail && xcodebuild clean docbuild -scheme CodeEditKit \
6 | -destination generic/platform=macos \
7 | -skipPackagePluginValidation \
8 | OTHER_DOCC_FLAGS="--transform-for-static-hosting --hosting-base-path CodeEditKit --output-path ./docs" | xcpretty
9 |
--------------------------------------------------------------------------------
/.github/scripts/tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ARCH=""
4 |
5 | if [ $1 = "arm" ]
6 | then
7 | ARCH="arm64"
8 | else
9 | ARCH="x86_64"
10 | fi
11 |
12 | echo "Building with arch: ${ARCH}"
13 |
14 | export LC_CTYPE=en_US.UTF-8
15 |
16 | set -o pipefail && arch -"${ARCH}" xcodebuild \
17 | -scheme CodeEditKit \
18 | -destination "platform=macos,arch=${ARCH}" \
19 | -skipPackagePluginValidation \
20 | clean test | xcpretty
21 |
--------------------------------------------------------------------------------
/.github/workflows/CI-pull-request.yml:
--------------------------------------------------------------------------------
1 | name: CI - Pull Request
2 | on:
3 | pull_request:
4 | branches:
5 | - 'main'
6 | workflow_dispatch:
7 | jobs:
8 | swiftlint:
9 | name: SwiftLint
10 | uses: ./.github/workflows/swiftlint.yml
11 | secrets: inherit
12 | test:
13 | name: Testing CodeEditKit
14 | needs: swiftlint
15 | uses: ./.github/workflows/tests.yml
16 | secrets: inherit
17 |
--------------------------------------------------------------------------------
/.github/workflows/CI-push.yml:
--------------------------------------------------------------------------------
1 | name: CI - Push to main
2 | on:
3 | push:
4 | branches:
5 | - 'main'
6 | workflow_dispatch:
7 | jobs:
8 | swiftlint:
9 | name: SwiftLint
10 | uses: ./.github/workflows/swiftlint.yml
11 | secrets: inherit
12 | test:
13 | name: Testing CodeEditKit
14 | needs: swiftlint
15 | uses: ./.github/workflows/tests.yml
16 | secrets: inherit
17 | build_documentation:
18 | name: Build Documentation
19 | needs: [swiftlint, test]
20 | uses: ./.github/workflows/build-documentation.yml
21 | secrets: inherit
22 |
--------------------------------------------------------------------------------
/.github/workflows/add-to-project.yml:
--------------------------------------------------------------------------------
1 | name: Add new issues to project
2 |
3 | on:
4 | issues:
5 | types:
6 | - opened
7 |
8 | jobs:
9 | add-to-project:
10 | name: Add new issues labeled with enhancement or bug to project
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/add-to-project@v0.4.0
14 | with:
15 | # You can target a repository in a different organization
16 | # to the issue
17 | project-url: https://github.com/orgs/CodeEditApp/projects/3
18 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
19 | labeled: enhancement, bug
20 | label-operator: OR
21 |
--------------------------------------------------------------------------------
/.github/workflows/build-documentation.yml:
--------------------------------------------------------------------------------
1 | name: build-documentation
2 | on:
3 | workflow_dispatch:
4 | workflow_call:
5 | jobs:
6 | build-docc:
7 | runs-on: [self-hosted, macOS]
8 | steps:
9 | - name: Checkout repository
10 | uses: actions/checkout@v3
11 | - name: Build Documentation
12 | run: exec ./.github/scripts/build-docc.sh
13 | - name: Init new repo in dist folder and commit generated files
14 | run: |
15 | cd docs
16 | git init
17 | git add -A
18 | git config --local user.email "action@github.com"
19 | git config --local user.name "GitHub Action"
20 | git commit -m 'deploy'
21 |
22 | - name: Force push to destination branch
23 | uses: ad-m/github-push-action@v0.6.0
24 | with:
25 | github_token: ${{ secrets.GITHUB_TOKEN }}
26 | branch: docs
27 | force: true
28 | directory: ./docs
29 |
30 | ############################
31 | ##### IMPORTANT NOTICE #####
32 | ############################
33 | # This was used to build the documentation catalog until
34 | # it didn't produce the 'documentation' directory anymore.
35 | #
36 | # - uses: fwcd/swift-docc-action@v1.0.2
37 | # with:
38 | # target: CodeEditKit
39 | # output: ./docs
40 | # hosting-base-path: CodeEditKit
41 | # disable-indexing: 'true'
42 | # transform-for-static-hosting: 'true'
43 | #
44 | # The command that this plugin uses is:
45 | #
46 | # swift package --allow-writing-to-directory ./docs generate-documentation \
47 | # --target CodeEditKit
48 | # --output-path ./docs
49 | # --hosting-base-path CodeEditKit
50 | # --disable-indexing
51 | # --transform-for-static-hosting
52 | #
53 | # We now use xcodebuild to build the documentation catalog instead.
54 | #
55 |
--------------------------------------------------------------------------------
/.github/workflows/swiftlint.yml:
--------------------------------------------------------------------------------
1 | name: SwiftLint
2 | on:
3 | workflow_dispatch:
4 | workflow_call:
5 | jobs:
6 | SwiftLint:
7 | runs-on: [self-hosted, macOS]
8 | steps:
9 | - uses: actions/checkout@v3
10 | - name: GitHub Action for SwiftLint with --strict
11 | run: swiftlint --strict
12 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 | on:
3 | workflow_dispatch:
4 | workflow_call:
5 | jobs:
6 | code-edit-kit-tests:
7 | name: Testing CodeEditKit
8 | runs-on: [self-hosted, macOS]
9 | steps:
10 | - name: Checkout repository
11 | uses: actions/checkout@v3
12 | - name: Testing Package
13 | run: exec ./.github/scripts/tests.sh arm
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .swiftpm
9 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - force_cast
3 |
4 | line_length: 160
5 |
6 | type_name:
7 | allowed_symbols: ['_']
8 | excluded:
9 | - ID
10 |
11 | identifier_name:
12 | min_length: 2
13 | allowed_symbols: ['$', '_']
14 | excluded:
15 | - id
16 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 CodeEdit
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "anycodable",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/Flight-School/AnyCodable",
7 | "state" : {
8 | "revision" : "862808b2070cd908cb04f9aafe7de83d35f81b05",
9 | "version" : "0.6.7"
10 | }
11 | },
12 | {
13 | "identity" : "concurrencyplus",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/ChimeHQ/ConcurrencyPlus",
16 | "state" : {
17 | "revision" : "8dc56499412a373d617d50d059116bccf44b9874",
18 | "version" : "0.4.2"
19 | }
20 | },
21 | {
22 | "identity" : "swiftlintplugin",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/lukepistrol/SwiftLintPlugin",
25 | "state" : {
26 | "revision" : "ea6d3ca895b49910f790e98e4b4ca658e0fe490e",
27 | "version" : "0.54.0"
28 | }
29 | }
30 | ],
31 | "version" : 2
32 | }
33 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.7
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "CodeEditKit",
7 | platforms: [
8 | .macOS(.v13)
9 | ],
10 | products: [
11 | .library(
12 | name: "CodeEditKit",
13 | type: .dynamic,
14 | targets: ["CodeEditKit"])
15 | ],
16 | dependencies: [
17 | .package(url: "https://github.com/ChimeHQ/ConcurrencyPlus", from: "0.4.1"),
18 | .package(
19 | url: "https://github.com/lukepistrol/SwiftLintPlugin",
20 | from: "0.2.2"
21 | ),
22 | .package(
23 | url: "https://github.com/Flight-School/AnyCodable",
24 | from: "0.6.0"
25 | )
26 | ],
27 | targets: [
28 | .target(
29 | name: "CodeEditKit",
30 | dependencies: ["AnyCodable", "ConcurrencyPlus"],
31 | plugins: [.plugin(name: "SwiftLint", package: "SwiftLintPlugin")]
32 | ),
33 | .testTarget(
34 | name: "CodeEditKitTests",
35 | dependencies: ["CodeEditKit"])
36 | ]
37 | )
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
CodeEditKit
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | CodeEditKit is a dynamic library which is shared between CodeEdit and its extensions. It allows them to communicate with and understand one another.
16 |
17 |
18 |
19 | 
20 | 
21 | 
22 | 
23 | [](https://discord.gg/vChUXVf9Em)
24 |
25 | ## Documentation
26 |
27 | The documentation for `CodeEditKit` can be viewed [here](https://codeeditapp.github.io/CodeEditKit/documentation/codeeditkit/).
28 |
29 | ## License
30 |
31 | Licensed under the [MIT license](https://github.com/CodeEditApp/CodeEdit/blob/main/LICENSE.md).
32 |
33 | ## Related Repositories
34 |
35 |
69 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/CodableColorArray.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 | @propertyWrapper
12 | public struct CodableColorArray {
13 | public var wrappedValue: [NSColor]
14 |
15 | public init(wrappedValue: [NSColor]) {
16 | self.wrappedValue = wrappedValue
17 | }
18 | }
19 |
20 | extension CodableColorArray: Codable {
21 | public init(from decoder: Decoder) throws {
22 | let container = try decoder.singleValueContainer()
23 | let data = try container.decode(Data.self)
24 |
25 | guard let color = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClass: NSColor.self, from: data) else {
26 | throw DecodingError.dataCorruptedError(
27 | in: container,
28 | debugDescription: "Invalid color"
29 | )
30 | }
31 | wrappedValue = color
32 | }
33 |
34 | public func encode(to encoder: Encoder) throws {
35 | var container = encoder.singleValueContainer()
36 | let data = try NSKeyedArchiver.archivedData(withRootObject: wrappedValue, requiringSecureCoding: true)
37 | try container.encode(data)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/CodableWrappers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File 2.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 06/12/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | @propertyWrapper
11 | public struct Encoded {
12 | public var wrappedValue: T
13 |
14 | public init(wrappedValue: T) {
15 | self.wrappedValue = wrappedValue
16 | }
17 |
18 | public var errorDescription: String? {
19 | do {
20 | _ = try JSONEncoder().encode(wrappedValue)
21 | return nil
22 | } catch {
23 | return error.localizedDescription
24 | }
25 | }
26 |
27 | public var projectedValue: Data? {
28 | try? JSONEncoder().encode(wrappedValue)
29 | }
30 | }
31 |
32 | @propertyWrapper
33 | public struct Decoded {
34 | public var wrappedValue: Data
35 |
36 | public init(wrappedValue: Data) {
37 | self.wrappedValue = wrappedValue
38 | }
39 |
40 | public var errorDescription: String? {
41 | do {
42 | _ = try JSONDecoder().decode(T.self, from: wrappedValue)
43 | return nil
44 | } catch {
45 | return error.localizedDescription
46 | }
47 | }
48 |
49 | public var projectedValue: T? {
50 | try? JSONDecoder().decode(T.self, from: wrappedValue)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/CodeEditExtension+Body.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 03/01/2023.
6 | //
7 |
8 | import Foundation
9 | import ExtensionKit
10 |
11 | struct EmptyAppExtensionScene: AppExtensionScene {
12 | var body: Never {
13 | fatalError()
14 | }
15 | }
16 |
17 | public extension CodeEditExtension {
18 | var body: some AppExtensionScene {
19 | EmptyAppExtensionScene()
20 | }
21 | }
22 |
23 | public extension CodeEditExtension where Self: SettingsExtension {
24 | var body: some AppExtensionScene {
25 | settingsScene
26 | }
27 | }
28 |
29 | public extension CodeEditExtension where Self: SidebarExtension {
30 | var body: some AppExtensionScene {
31 | sidebarScenes
32 | }
33 | }
34 |
35 | public extension CodeEditExtension where Self: SettingsExtension & SidebarExtension {
36 | @AppExtensionSceneBuilder
37 | var body: some AppExtensionScene {
38 | settingsScene
39 | sidebarScenes
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/CodeEditExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import ExtensionKit
9 | import ExtensionFoundation
10 |
11 | public protocol CodeEditExtension: AppExtension {
12 |
13 | /// UI scenes of the extension.
14 | associatedtype Body: AppExtensionScene
15 |
16 | /// Extension Configuration.
17 | associatedtype Configuration = AppExtensionSceneConfiguration
18 |
19 | /// A brief description of the extension. Should be max a few words.
20 | var description: String { get }
21 |
22 | /// A list of Entitlements the app needs, e.g. Network Access.
23 | var entitlements: [Entitlement] { get }
24 |
25 | /// UI scenes of the extension.
26 | /// Use the default implementation.
27 | var body: Body { get }
28 | }
29 |
30 | extension CodeEditExtension {
31 |
32 | var extensionURL: URL {
33 | Bundle.main.bundleURL
34 | }
35 |
36 | func gatherAvailableExtensions() -> [ExtensionKind] {
37 | var extensions: [ExtensionKind] = []
38 |
39 | if self is any SettingsExtension {
40 | extensions.append(.settings)
41 | }
42 |
43 | if let self = self as? any SidebarExtension {
44 | extensions.append(contentsOf: self.availableExtensions)
45 | }
46 |
47 | return extensions
48 | }
49 | }
50 |
51 | public extension CodeEditExtension where Self: AnyObject {
52 | /// XPC Configuration for communication with CodeEdit.
53 | /// Use the default implementation.
54 | var configuration: AppExtensionSceneConfiguration {
55 | AppExtensionSceneConfiguration(self.body, configuration: SettingsExtensionConfiguration(self))
56 | }
57 | }
58 |
59 | struct SettingsExtensionConfiguration: AppExtensionConfiguration {
60 | public func accept(connection: NSXPCConnection) -> Bool {
61 | guard let appExtension else {
62 | return false
63 | }
64 |
65 | connection.exportedInterface = .init(with: XPCWrappable.self)
66 | connection.exportedObject = XPCWrapper(appExtension)
67 | connection.resume()
68 |
69 | return true
70 | }
71 |
72 | weak var appExtension: E?
73 |
74 | public init(_ appExtension: E) {
75 | self.appExtension = appExtension
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Debugging/Logging.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 22/05/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
11 | let formedString = items.map { String(describing: $0) }.joined(separator: separator)
12 | NSLog(formedString)
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/CodeEditExtension.md:
--------------------------------------------------------------------------------
1 | # ``CodeEditKit/CodeEditExtension``
2 |
3 | @Metadata {
4 | @DocumentationExtension(mergeBehavior: append)
5 | }
6 |
7 | The base extension.
8 |
9 | ## Overview
10 |
11 | All extensions supported by CodeEdit are defined as a ``CodeEditExtension``.
12 | This protocol provides basic information about the extension, such as the ``description``, name and ``entitlements``.
13 |
14 | This type is the entrypoint of your extension, so it should be marked with `@main`.
15 |
16 | Additional extensions can be defined by extending the class that implements ``CodeEditExtension``.
17 |
18 | ## Example
19 |
20 | ```swift
21 | @main
22 | final class ExampleExtension: CodeEditExtension {
23 | var description: String = "test"
24 |
25 | var entitlements: [Entitlement] = [.currentfile]
26 | }
27 |
28 | // Add a Settings Pane
29 | extension ExampleExtension: SettingsExtension {
30 | var settings: some View {
31 | SettingsView()
32 | }
33 | }
34 |
35 | // The Settings View
36 | struct SettingsView: View {
37 | @State var enabled = false
38 | var body: some View {
39 | Toggle("Enabled", isOn: $enabled)
40 | }
41 | }
42 | ```
43 |
44 | > Info: ``CodeEditExtension`` Conforms to ObservableObject and is automatically injected into the environment of each View. This way, it's possible to have shared state between different UI extensions by observing the object in a View.
45 | > ```swift
46 | > struct AView: View {
47 | > @EnvironmentObject var main: ExampleExtension
48 | > }
49 | > ```
50 |
51 |
52 |
53 | ## Topics
54 |
55 | ### Group
56 |
57 | - ``Symbol``
58 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/Commands.md:
--------------------------------------------------------------------------------
1 | # Commands
2 |
3 | Quick actions
4 |
5 | ## Overview
6 |
7 | ## Topics
8 |
9 | ### Group
10 |
11 | - ``Symbol``
12 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/CreatingProject.md:
--------------------------------------------------------------------------------
1 | # Project Setup
2 |
3 | ## Overview
4 |
5 | To start developing extensions, you first need to configure your project.
6 |
7 | ## Steps
8 |
9 | This is a WIP, and should be explained better.
10 |
11 | 1. Create a new Xcode project. As the template, choose "App" (make sure you select the macOS tab, not the multiplatform tab)
12 | 2. Once created, go to File -> New -> Target. In the macOS tab, choose "Generic Extension". Enter a name, enable the checkbox for UI support, and leave the other things default. Click "activate" on the popup.
13 | 3. In the folder of your new extension, a new "Info" plist file should have been created. In it, replace the value of EXExtensionPointIdentifier with "codeedit.extension" (no quotation marks)
14 | 4. Next, add CodeEditKit as a dependency. If you have CodeEditKit as a local package, first make sure no other Xcode projects that also rely on that package are opened. Otherwise, Xcode won't find the package.
15 | 5. Go to the project configuration, select the target with the name of your extension, and go to the general tab. There, add CodeEditKit to the "Frameworks and Libraries" section.
16 | 6. Now, go to the main swift file in the extension folder. Remove all autogenerated code, we won't need it. Instead, import CodeEditKit and follow the steps for ``CodeEditExtension``.
17 | 7. Lastly, press run! The extension will compile and Xcode will ask which app to start. Choose CodeEdit. To prevent this popup from always showing, you can edit the extension scheme to always choose CodeEdit.
18 |
19 | ## Topics
20 |
21 | ### Group
22 |
23 | - ``Symbol``
24 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/Documentation.md:
--------------------------------------------------------------------------------
1 | # ``CodeEditKit``
2 |
3 | CodeEditKit is a dynamic library which is shared between CodeEdit and it's extensions. It allows them to communicate with and understand one another.
4 |
5 | ## Overview
6 |
7 | Extensions are an essential part of CodeEdit. They can extend the languages CodeEdit supports, add custom actions and behaviors, and even provide custom views of certain data.
8 |
9 | CodeEditKit aims to provide an easy and straightforward API to implement these extensions, with modern technologies like Swift, SwiftUI and ExtensionKit.
10 |
11 | ### Extension Types
12 |
13 | There are lots of extension types you can use to extend the functionality of CodeEdit.
14 |
15 | #### General Extensions
16 | - (WIP)
17 | - (Not Started)
18 |
19 | #### Language Extensions
20 | - (Not Started)
21 | - (WIP)
22 |
23 | #### UI Extensions
24 | - ``SidebarExtension`` (Beta)
25 | - (WIP)
26 | - (Beta)
27 |
28 | ### Getting Started
29 | To setup your Xcode project, have a look at the section.
30 |
31 | Next, have a look at ``CodeEditExtension``. This is the base protocol that will define the main structure of your extension.
32 | All extensions that you'll add will extend (:p) this type.
33 |
34 | Finally, try adding an extension to your newly created type. A good first recommendation is the extension, as you'll likely need this later.
35 |
36 | ### Development & Debugging
37 | Developing extensions with ExtensionKit has a few annoyances:
38 | - Prints do not appear in the Xcode console. Instead, the debug information of CodeEdit (not the extension) is printed.
39 | - Modifying extension requires CodeEdit reload.
40 | - An extension needs to be sandboxed.
41 |
42 | CodeEdit tries to overcome these issues by offering a few solutions.
43 |
44 | First, CodeEdit is able to capture log messages (which are also sent to Console.app). Therefore, CodeEdit provides a custom ``print(_:separator:terminator:)`` function which works the same as Swift's one, except it sends its input as an OSLog instead of printing it to stdout. With this change, you are able to view your extensions' outputs in CodeEdit's console. Later on, an extra toggle will be provided which will also print the changes to Xcode's console.
45 |
46 | Additionally, if you choose to use the powerful Logger system to debug, CodeEdit provides support for various log types and will color them accordingly.
47 |
48 | Next, CodeEdit supports a way of hot reloading your extension. Instead of relaunching CodeEdit and the extension, only the extension is restarted, resulting in faster load times. To make use of this, build your extension instead of running it (⌘B instead of ⌘R). Note that this feature is still in beta and may behave unexpectedly.
49 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/GenericExtension.md:
--------------------------------------------------------------------------------
1 | # ``CodeEditKit/GenericExtension``
2 |
3 | Summary
4 |
5 | ## Overview
6 |
7 | Text
8 |
9 | > Important: If you instantiate multiple instances of a struct that implements ``GenericExtension``, replace the default implementation of ``id`` with an unique ``id``. Otherwise, they'll have the same ``id`` which will cause trouble.
10 |
11 | ## Topics
12 |
13 | ### Group
14 |
15 | - ``Symbol``
16 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/LanguageServers.md:
--------------------------------------------------------------------------------
1 | # Language Servers
2 |
3 | Summary
4 |
5 | ## Overview
6 |
7 | Text
8 |
9 | ## Topics
10 |
11 | ### Group
12 |
13 | - ``Symbol``
14 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/Settings.md:
--------------------------------------------------------------------------------
1 | # Settings
2 |
3 | Summary
4 |
5 | ## Overview
6 |
7 | The ``SettingsExtension`` allows an extension to provide additional settings to the settings window of CodeEdit.
8 | Extension-specific settings can be configured this way.
9 |
10 | ``SettingsExtension`` expects a SwiftUI View and doesn't take care of the storage of settings. Therefore, you are responsible for storing your extensions' settings in an appropriate manner.
11 |
12 | ## Example
13 |
14 | ```swift
15 | @main
16 | final class SettingsExampleExtension: CodeEditExtension {
17 |
18 | var description: String = ""
19 |
20 | var entitlements: [Entitlement] = [.currentfile]
21 |
22 | }
23 |
24 | extension SettingsExampleExtension: SettingsExtension {
25 | var settings: some View {
26 | SettingsView()
27 | }
28 | }
29 |
30 | struct SettingsView: View {
31 | @AppStorage("appstoragekey") var appstoragekey = false
32 |
33 | var body: some View {
34 | Toggle("App Storage Key", isOn: $appstoragekey)
35 | }
36 | }
37 | ```
38 |
39 | ## Topics
40 |
41 | ### Group
42 |
43 | - ``Symbol``
44 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/Sidebars.md:
--------------------------------------------------------------------------------
1 | # ``CodeEditKit/SidebarExtension``
2 |
3 | @Metadata {
4 | @DocumentationExtension(mergeBehavior: append)
5 | }
6 |
7 | Navigator & Inspector UI Extensions
8 |
9 | ## Overview
10 |
11 | The ``SidebarExtension`` protocol takes care of Navigator and Inspector extensions. These extensions are added to the tabbar of each sidebar, and can be selected by the user.
12 |
13 | ## Example
14 |
15 | ```swift
16 | @main
17 | final class SidebarExtensionExample: CodeEditExtension {
18 | var description: String = ""
19 |
20 | var entitlements: [Entitlement] = []
21 | }
22 |
23 | extension SidebarExtensionExample: SidebarExtension {
24 | var sidebars: some Sidebar {
25 | Inspector(id: "umbrellaInspector") {
26 | Form {
27 | Text("Hello, world!")
28 | }
29 | .formStyle(.grouped)
30 | }
31 | .help("Umbrella Inspector")
32 | .icon("umbrella")
33 |
34 | Navigator(id: "carrotNavigator") {
35 | Form {
36 | Text("Hello, world!")
37 | }
38 | .formStyle(.grouped)
39 | }
40 | .help("Carrot Navigator")
41 | .icon("carrot")
42 | }
43 | }
44 |
45 | ```
46 |
47 | ## Topics
48 |
49 | ### Sidebar Modifiers
50 |
51 | - ``Sidebar/help(_:)``
52 | - ``Sidebar/icon(_:)``
53 | - ``Sidebar/description(_:)``
54 | - ``Sidebar/title(_:)``
55 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/Snippets.md:
--------------------------------------------------------------------------------
1 | # Snippets
2 |
3 | Summary
4 |
5 | ## Overview
6 |
7 | Text
8 |
9 | ## Topics
10 |
11 | ### Group
12 |
13 | - ``Symbol``
14 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/Themes.md:
--------------------------------------------------------------------------------
1 | # Themes
2 |
3 | Summary
4 |
5 | ## Overview
6 |
7 | Text
8 |
9 | ## Topics
10 |
11 | ### Group
12 |
13 | - ``Symbol``
14 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Documentation.docc/ToolbarItems.md:
--------------------------------------------------------------------------------
1 | # Toolbar Items
2 |
3 | Summary
4 |
5 | ## Overview
6 |
7 | Text
8 |
9 | ## Topics
10 |
11 | ### Group
12 |
13 | - ``Symbol``
14 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/Entitlements.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum Entitlement: CustomStringConvertible, Codable, CaseIterable {
11 | case workspace
12 | case currentfile
13 |
14 | public var description: String {
15 | switch self {
16 | case .workspace:
17 | return "Workspace"
18 | case .currentfile:
19 | return "Current File"
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/ExtensionKind.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public enum ExtensionKind: Codable, Hashable, CustomStringConvertible {
11 | case sidebarItem(data: ResolvedSidebar.SidebarStore)
12 | case action(actionID: String)
13 | case theme(themeID: String)
14 | case settings
15 |
16 | public var description: String {
17 | switch self {
18 | case .sidebarItem(let data):
19 | return "Sidebar with ID \(data.sceneID)"
20 | case .action(let actionID):
21 | return "Action with ID \(actionID)"
22 | case .theme(let themeID):
23 | return "Theme with ID \(themeID)"
24 | case .settings:
25 | return "Settings"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/GenericScene.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 02/01/2023.
6 | //
7 |
8 | import SwiftUI
9 | import ExtensionKit
10 |
11 | public enum Callbacks: Codable {
12 | case openWindow(id: String)
13 | }
14 |
15 | struct GenericScene: AppExtensionScene {
16 |
17 | var sceneID: String
18 |
19 | @ViewBuilder
20 | var content: Content
21 |
22 | var environmentWrapper = EnvironmentPublisher()
23 |
24 | var connection: NSXPCConnection?
25 |
26 | var body: some AppExtensionScene {
27 | PrimitiveAppExtensionScene(id: sceneID) {
28 | GeneralSettingsView(environmentWrapper: environmentWrapper) {
29 | content
30 | .environment(\.ceOpenWindow) { id in
31 | try await environmentWrapper.update(callback: .openWindow(id: id))
32 | }
33 | }
34 | } onConnection: { connection in
35 | connection.exportedInterface = .init(with: EnvironmentPublisherObjc.self)
36 | connection.remoteObjectInterface = .init(with: EnvironmentPublisherObjc.self)
37 | connection.exportedObject = environmentWrapper
38 | environmentWrapper.connection = connection
39 | connection.resume()
40 |
41 | return true
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Environment/CEEnvironment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 | import AnyCodable
10 | import SwiftUI
11 |
12 | public struct _CEEnvironment: Codable, Equatable {
13 |
14 | public var test = false
15 | public var complexValue: [String] = []
16 |
17 | var otherKeys: [String: AnyCodable] = [:]
18 |
19 | mutating func update(_ keyPath: WritableKeyPath, _ value: Value) {
20 | self[keyPath: keyPath] = value
21 | }
22 |
23 | public subscript(key: K.Type) -> K.Value where K: CEEnvironmentKey {
24 | get {
25 | guard let data = otherKeys[key.identifier] else { return key.defaultValue }
26 | return data.value as! K.Value
27 | }
28 | set { otherKeys[key.identifier] = AnyCodable(newValue) }
29 | }
30 | }
31 |
32 | @propertyWrapper
33 | public struct CEEnvironment: DynamicProperty {
34 | @Environment(\._ceEnvironment) var environment
35 |
36 | public init(_ keyPath: KeyPath<_CEEnvironment, Value>) {
37 | self.keyPath = keyPath
38 | }
39 |
40 | let keyPath: KeyPath<_CEEnvironment, Value>
41 |
42 | public var wrappedValue: Value {
43 | environment[keyPath: keyPath]
44 | }
45 | }
46 |
47 | public struct _CEEnvironmentKey: EnvironmentKey {
48 | public static let defaultValue = _CEEnvironment()
49 | }
50 |
51 | public extension EnvironmentValues {
52 | var _ceEnvironment: _CEEnvironmentKey.Value {
53 | get {
54 | return self[_CEEnvironmentKey.self]
55 | }
56 | set {
57 | self[_CEEnvironmentKey.self] = newValue
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Environment/CEEnvironmentKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol CEEnvironmentKey {
11 |
12 | /// The associated type representing the type of the environment key's
13 | /// value.
14 | associatedtype Value: Codable
15 |
16 | static var identifier: String { get }
17 |
18 | /// The default value for the environment key.
19 | static var defaultValue: Self.Value { get }
20 | }
21 |
22 | public extension CEEnvironmentKey {
23 | static var identifier: String {
24 | String(describing: Self.self)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Environment/CEEnvironmentModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 02/01/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct CEEnvironmentModifier: ViewModifier {
11 | var keyPath: WritableKeyPath<_CEEnvironment, Value>
12 | var value: Value
13 |
14 | public func body(content: Content) -> some View {
15 | content
16 | .transformEnvironment(\._ceEnvironment) { env in
17 | env.update(keyPath, value)
18 | }
19 | }
20 | }
21 |
22 | public extension View {
23 | func ceEnvironment(_ keyPath: WritableKeyPath<_CEEnvironment, Value>, _ value: Value) -> some View {
24 | return modifier(CEEnvironmentModifier(keyPath: keyPath, value: value))
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Environment/CEOpenWindowEnvKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 02/01/2023.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | public struct CEOpenWindowEnvKey: EnvironmentKey {
12 | public static var defaultValue: (String) async throws -> Void = { _ in }
13 | }
14 |
15 | public extension EnvironmentValues {
16 | var ceOpenWindow: CEOpenWindowEnvKey.Value {
17 | get { self[CEOpenWindowEnvKey.self] }
18 | set { self[CEOpenWindowEnvKey.self] = newValue }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Environment/EnvironmentObjcPublisher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 | import ConcurrencyPlus
10 |
11 | @objc public protocol EnvironmentPublisherObjc {
12 | func publishEnvironment(data: Data)
13 | }
14 |
15 | public class EnvironmentPublisher: ObservableObject, EnvironmentPublisherObjc {
16 |
17 | @Published var environment = _CEEnvironment()
18 |
19 | var connection: NSXPCConnection?
20 |
21 | /// Send callbacks from functions
22 | func update(@Encoded callback: Callbacks) async throws {
23 | guard let $callback else { return }
24 | try await connection!.withService { (service: EnvironmentPublisherObjc) in
25 | service.publishEnvironment(data: $callback)
26 | }
27 | }
28 |
29 | public func publishEnvironment(data: Data) {
30 | @Decoded<_CEEnvironment> var data = data
31 | guard let $data else { return }
32 | environment = $data
33 |
34 | print("update: received data \($data)")
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Settings/GeneralSettingsScene.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import ExtensionKit
11 |
12 | struct GeneralSettingsScene: AppExtensionScene {
13 |
14 | @ViewBuilder
15 | let content: () -> Content
16 |
17 | init(content: @escaping () -> Content) {
18 | self.content = content
19 | }
20 |
21 | var connection: NSXPCConnection?
22 | var environmentWrapper = EnvironmentPublisher()
23 |
24 | var body: some AppExtensionScene {
25 | PrimitiveAppExtensionScene(id: "Settings") {
26 | GeneralSettingsView(environmentWrapper: environmentWrapper) {
27 | Form {
28 | content()
29 | }
30 | .formStyle(.grouped)
31 | }
32 | } onConnection: { connection in
33 | connection.exportedInterface = .init(with: EnvironmentPublisherObjc.self)
34 | connection.exportedObject = environmentWrapper
35 | connection.resume()
36 |
37 | return true
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Settings/GeneralSettingsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct GeneralSettingsView: View {
12 |
13 | @StateObject var environmentWrapper: EnvironmentPublisher
14 | @ViewBuilder var content: Content
15 |
16 | var body: some View {
17 | content
18 | .environment(\._ceEnvironment, environmentWrapper.environment)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Settings/SettingsExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import ExtensionKit
11 |
12 | public protocol SettingsExtension: ObservableObject {
13 | associatedtype SettingsBody: View
14 |
15 | @ViewBuilder
16 | var settings: SettingsBody { get }
17 | }
18 |
19 | public extension SettingsExtension {
20 | var settingsScene: some AppExtensionScene {
21 | GenericScene(sceneID: "Settings") {
22 | Form {
23 | self.settings
24 | .environmentObject(self)
25 | }
26 | .formStyle(.grouped)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Sidebar/Inspector.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 25/03/2023.
6 | //
7 |
8 | import SwiftUI
9 | import ExtensionKit
10 |
11 | public struct Inspector: Sidebar {
12 | public var body: Never {
13 | fatalError()
14 | }
15 |
16 | var id: String
17 |
18 | public init(id: String, content: () -> Content) {
19 | self.id = id
20 | self.content = content()
21 | }
22 |
23 | @ViewBuilder var content: Content
24 |
25 | @_spi(CodeEdit)
26 | public func resolve(environment: some ObservableObject) -> [ResolvedSidebar] {
27 | let scene = PrimitiveAppExtensionScene(id: id) {
28 | VStack(alignment: .leading) {
29 | content
30 | .scrollContentBackground(.hidden)
31 | .environmentObject(environment)
32 | }
33 | .frame(maxWidth: .infinity, maxHeight: .infinity)
34 | }
35 | let store = ResolvedSidebar.SidebarStore(sceneID: id, kind: .inspector)
36 | return [ResolvedSidebar(scene: scene, store: store)]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Sidebar/Navigator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 25/03/2023.
6 | //
7 |
8 | import SwiftUI
9 | import ExtensionKit
10 |
11 | public struct Navigator: Sidebar {
12 | public var body: Never {
13 | fatalError()
14 | }
15 |
16 | var id: String
17 |
18 | public init(id: String, content: () -> Content) {
19 | self.id = id
20 | self.content = content()
21 | }
22 |
23 | @ViewBuilder var content: Content
24 |
25 | @_spi(CodeEdit)
26 | public func resolve(environment: some ObservableObject) -> [ResolvedSidebar] {
27 | let scene = PrimitiveAppExtensionScene(id: id) {
28 | VStack(alignment: .leading) {
29 | content
30 | .scrollContentBackground(.hidden)
31 | .environmentObject(environment)
32 | }
33 | .frame(maxWidth: .infinity, maxHeight: .infinity)
34 | }
35 | let store = ResolvedSidebar.SidebarStore(sceneID: id, kind: .navigator)
36 | return [ResolvedSidebar(scene: scene, store: store)]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Sidebar/ResolvedSidebar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 24/03/2023.
6 | //
7 |
8 | import Foundation
9 | import ExtensionKit
10 |
11 | public struct ResolvedSidebar {
12 |
13 | public enum Kind: Codable, Hashable {
14 | case navigator, inspector
15 | }
16 |
17 | var scene: PrimitiveAppExtensionScene
18 |
19 | var store: SidebarStore
20 |
21 | public struct SidebarStore: Codable, Hashable {
22 | public var sceneID: String
23 | public var icon: String?
24 | public var help: String?
25 | public var kind: Kind
26 | }
27 |
28 | var extensionKind: ExtensionKind {
29 | .sidebarItem(data: store)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Sidebar/Sidebar+Help.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 24/03/2023.
6 | //
7 |
8 | import SwiftUI
9 | import ExtensionKit
10 |
11 | struct SidebarHelpModifier: Sidebar {
12 | var help: String
13 | var content: Content
14 |
15 | var body: Never {
16 | fatalError()
17 | }
18 |
19 | func resolve(environment: some ObservableObject) -> [ResolvedSidebar] {
20 | var sidebars = self.content.resolve(environment: environment)
21 | for index in sidebars.indices {
22 | sidebars[index].store.help = help
23 | }
24 | return sidebars
25 | }
26 | }
27 |
28 | struct SidebarIconModifier: Sidebar {
29 | var icon: String
30 | var content: Content
31 |
32 | var body: Never {
33 | fatalError()
34 | }
35 |
36 | func resolve(environment: some ObservableObject) -> [ResolvedSidebar] {
37 | var sidebars = self.content.resolve(environment: environment)
38 | for index in sidebars.indices {
39 | sidebars[index].store.icon = icon
40 | }
41 | return sidebars
42 | }
43 | }
44 |
45 | public extension Sidebar {
46 | func help(_ message: String) -> some Sidebar {
47 | SidebarHelpModifier(help: message, content: self)
48 | }
49 |
50 | func icon(_ systemName: String) -> some Sidebar {
51 | SidebarIconModifier(icon: systemName, content: self)
52 | }
53 |
54 | func title(_ title: String) -> some Sidebar {
55 | self
56 | }
57 |
58 | func description(_ message: String) -> some Sidebar {
59 | self
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Sidebar/Sidebar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 24/03/2023.
6 | //
7 |
8 | import Foundation
9 | import ExtensionKit
10 |
11 | public protocol Sidebar {
12 | associatedtype Body: Sidebar
13 |
14 | @SidebarBuilder
15 | var body: Body { get }
16 |
17 | @_spi(CodeEdit)
18 | func resolve(environment: some ObservableObject) -> [ResolvedSidebar]
19 | }
20 |
21 | public extension Sidebar {
22 | func resolve(environment: some ObservableObject) -> [ResolvedSidebar] {
23 | body.resolve(environment: environment)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Sidebar/SidebarBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 24/03/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | @resultBuilder
11 | public struct SidebarBuilder {
12 | public static func buildPartialBlock(first: Never) -> Never {}
13 |
14 | public static func buildPartialBlock(first: some Sidebar) -> some Sidebar {
15 | first
16 | }
17 |
18 | public static func buildPartialBlock(accumulated: some Sidebar, next: some Sidebar) -> some Sidebar {
19 | TupleSidebar(accumulated, next)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Sidebar/SidebarExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 | import ExtensionKit
10 | import SwiftUI
11 |
12 | public protocol SidebarExtension: ObservableObject {
13 |
14 | associatedtype SidebarBody: Sidebar
15 |
16 | @SidebarBuilder
17 | var sidebars: SidebarBody { get }
18 | }
19 |
20 | public extension SidebarExtension {
21 | var sidebarScenes: some AppExtensionScene {
22 | sidebars.resolve(environment: self).map(\.scene)
23 | }
24 |
25 | var availableExtensions: [ExtensionKind] {
26 | sidebars.resolve(environment: self).map(\.extensionKind)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Sidebar/SidebarNever.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 24/03/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Swift.Never: Sidebar {
11 |
12 | public var body: Swift.Never {
13 | fatalError()
14 | }
15 |
16 | @_spi(CodeEdit)
17 | public func resolve(environment: some ObservableObject) -> [ResolvedSidebar] {
18 | fatalError()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/UIExtensions/Sidebar/TupleSidebar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 24/03/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | struct TupleSidebar: Sidebar {
11 | var c0: C0
12 | var c1: C1
13 |
14 | init(_ c0: C0, _ c1: C1) {
15 | self.c0 = c0
16 | self.c1 = c1
17 | }
18 |
19 | var body: Never {
20 | fatalError()
21 | }
22 |
23 | func resolve(environment: some ObservableObject) -> [ResolvedSidebar] {
24 | c0.resolve(environment: environment) + c1.resolve(environment: environment)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/CodeEditKit/XPCWrapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Wouter Hennen on 30/12/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | public typealias XPCReply = (Data?, Error?) -> Void
11 |
12 | public enum XPCError: Error {
13 | case extensionDoesNotExist(description: String)
14 | case identifierDoesNotExist(description: String)
15 | }
16 |
17 | @objc
18 | public protocol XPCWrappable {
19 | func getExtensionURL(reply: @escaping XPCReply)
20 |
21 | func getExtensionKinds(reply: @escaping XPCReply)
22 |
23 | func getExtensionProcessIdentifier(reply: @escaping (Int32) -> Void)
24 |
25 | func doAction(with identifier: String, reply: @escaping XPCReply)
26 |
27 | func isDebug(reply: @escaping (Bool) -> Void)
28 | }
29 |
30 | class XPCWrapper: XPCWrappable {
31 |
32 | var ext: any CodeEditExtension
33 |
34 | init(_ ext: any CodeEditExtension) {
35 | self.ext = ext
36 | }
37 |
38 | func getExtensionURL(reply: @escaping XPCReply) {
39 | do {
40 | let encoded = try JSONEncoder().encode(ext.extensionURL)
41 | reply(encoded, nil)
42 | } catch {
43 | reply(nil, error)
44 | }
45 | }
46 |
47 | func getExtensionKinds(reply: @escaping XPCReply) {
48 | do {
49 | let encoded = try JSONEncoder().encode(ext.gatherAvailableExtensions())
50 | reply(encoded, nil)
51 | } catch {
52 | reply(nil, error)
53 | }
54 | }
55 |
56 | func getExtensionProcessIdentifier(reply: @escaping (Int32) -> Void) {
57 | reply(ProcessInfo.processInfo.processIdentifier)
58 | }
59 |
60 | func doAction(with identifier: String, reply: @escaping XPCReply) {
61 |
62 | }
63 |
64 | func isDebug(reply: @escaping (Bool) -> Void) {
65 | #if DEBUG
66 | reply(true)
67 | #else
68 | reply(false)
69 | #endif
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Tests/CodeEditKitTests/CEExtensionKitTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import CodeEditKit
3 |
4 | final class CEExtensionKitTests: XCTestCase {
5 |
6 | // This is only a sample test case
7 | // until real unit tests are established
8 | func testExample() throws {
9 | XCTAssertTrue(true)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------