├── .github ├── CodeEditCLI-Icon-128@2x.png └── workflows │ ├── CI-deploy.yml │ ├── CI-pull-request.yml │ ├── CI-push.yml │ ├── add-to-project.yml │ ├── build.yml │ ├── deploy.yml │ └── swiftlint.yml ├── .gitignore ├── .swiftlint.yml ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md └── Sources └── CodeEditCLI ├── Open.swift ├── Version.swift └── main.swift /.github/CodeEditCLI-Icon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeEditApp/CodeEditCLI/f557825656739b37375862f71c2e2bc73cba1ab6/.github/CodeEditCLI-Icon-128@2x.png -------------------------------------------------------------------------------- /.github/workflows/CI-deploy.yml: -------------------------------------------------------------------------------- 1 | name: CI - Deploy 2 | on: 3 | push: 4 | tags: 5 | - '*.*.*' 6 | workflow_dispatch: 7 | jobs: 8 | swiftlint: 9 | name: SwiftLint 10 | uses: ./.github/workflows/swiftlint.yml 11 | secrets: inherit 12 | test: 13 | name: Build CodeEdit CLI 14 | needs: swiftlint 15 | uses: ./.github/workflows/build.yml 16 | secrets: inherit 17 | deploy: 18 | name: Deploy CodeEdit CLI 19 | needs: [swiftlint, test] 20 | uses: ./.github/workflows/deploy.yml 21 | secrets: inherit 22 | -------------------------------------------------------------------------------- /.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: Build CodeEdit CLI 14 | needs: swiftlint 15 | uses: ./.github/workflows/build.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: Build CodeEdit CLI 14 | needs: swiftlint 15 | uses: ./.github/workflows/build.yml 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.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.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | workflow_dispatch: 4 | workflow_call: 5 | jobs: 6 | build-codeedit-cli: 7 | name: Building CodeEdit CLI 8 | runs-on: [self-hosted, macOS] 9 | steps: 10 | - name: Checkout Repository 11 | uses: actions/checkout@v3 12 | 13 | - name: Building 14 | run: swift build -c release --arch arm64 --arch x86_64 15 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_dispatch: 4 | workflow_call: 5 | 6 | jobs: 7 | deploy-codeedit-cli: 8 | name: Deploying CodeEdit CLI 9 | runs-on: [self-hosted, macOS] 10 | steps: 11 | - name: Checkout Repository 12 | uses: actions/checkout@v3 13 | 14 | - name: Install codesign certificate 15 | env: 16 | # DEV_CERT_B64: Base64-encoded developer certificate as .p12 17 | # DEV_CERT_PWD: Developer certificate .p12 password 18 | # KEYCHAIN_TIMEOUT: Lock keychain after timeout interval 19 | # https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development 20 | DEV_CERT_B64: ${{ secrets.DEV_CERT_B64 }} 21 | DEV_CERT_PWD: ${{ secrets.DEV_CERT_PWD }} 22 | KEYCHAIN_TIMEOUT: 21600 23 | run: | 24 | DEV_CERT_P12="$RUNNER_TEMP/dev_cert.p12" 25 | KEYCHAIN_DB="$RUNNER_TEMP/keychain.keychain-db" 26 | KEYCHAIN_PWD=$(openssl rand -base64 24) 27 | security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN_DB" 28 | security set-keychain-settings -lut "$KEYCHAIN_TIMEOUT" "$KEYCHAIN_DB" 29 | security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN_DB" 30 | echo -n "$DEV_CERT_B64" | base64 --decode -o "$DEV_CERT_P12" 31 | security import "$DEV_CERT_P12" -P "$DEV_CERT_PWD" -A -t cert -f pkcs12 -k "$KEYCHAIN_DB" 32 | security list-keychain -d user -s "$KEYCHAIN_DB" 33 | 34 | - name: Building 35 | run: | 36 | swift build -c release --arch arm64 --arch x86_64 37 | 38 | - name: Sign 39 | env: 40 | CODESIGN_SIGN: ${{ secrets.CODESIGN_SIGN }} 41 | run: | 42 | security find-identity -p basic -v 43 | codesign --sign "$CODESIGN_SIGN" --prefix app.codeedit.CodeEdit. --options=runtime --verbose --timestamp .build/apple/Products/Release/codeedit 44 | 45 | - name: Zip 46 | run: | 47 | cd .build/apple/Products/Release 48 | zip -r codeedit-cli.zip codeedit 49 | cd ../../../../ 50 | 51 | - name: Notarize 52 | env: 53 | APPLE_ID: ${{ secrets.APPLE_ID }} 54 | APPLE_ID_PWD: ${{ secrets.APPLE_ID_PWD }} 55 | APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} 56 | run: | 57 | xcrun notarytool submit ".build/apple/Products/Release/codeedit-cli.zip" --apple-id "$APPLE_ID" --password "$APPLE_ID_PWD" --team-id "$APPLE_TEAM_ID" --verbose --wait --output-format plist > "NotarizationResponse.plist" 58 | status=`/usr/libexec/PlistBuddy -c "Print :status" "NotarizationResponse.plist"` 59 | if [[ $status != "Accepted" ]]; then 60 | exit 999 61 | fi 62 | 63 | - name: Create Release 64 | id: create_release 65 | uses: actions/create-release@v1 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 68 | with: 69 | tag_name: ${{ github.ref }} 70 | release_name: ${{ github.ref }} 71 | draft: false 72 | prerelease: false 73 | 74 | - name: Upload Release Asset 75 | uses: actions/upload-release-asset@v1 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | with: 79 | upload_url: ${{ steps.create_release.outputs.upload_url }} 80 | asset_path: .build/apple/Products/Release/codeedit-cli.zip 81 | asset_name: codeedit-cli-universal-binary.zip 82 | asset_content_type: application/zip 83 | 84 | - name: Clean up keychain 85 | if: ${{ always() }} 86 | run: | 87 | security delete-keychain "$RUNNER_TEMP/keychain.keychain-db" 88 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | 2 | identifier_name: 3 | allowed_symbols: ['_'] 4 | 5 | large_tuple: 6 | warning: 5 7 | 8 | excluded: 9 | - .build 10 | - .git 11 | - .swiftpm 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prefix ?= /usr/local 2 | bindir = $(prefix)/bin 3 | binname = "codeedit" 4 | 5 | build: 6 | swift build -c release --disable-sandbox 7 | 8 | install: build 9 | install -d "$(bindir)" 10 | install ".build/release/$(binname)" "$(bindir)" 11 | 12 | uninstall: 13 | rm -rf "$(bindir)/$(binname)" 14 | 15 | clean: 16 | rm -rf .build 17 | 18 | .PHONY: build install uninstall clean 19 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-argument-parser", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/apple/swift-argument-parser", 7 | "state" : { 8 | "revision" : "fddd1c00396eed152c45a46bea9f47b98e59301d", 9 | "version" : "1.2.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CodeEditCLI", 8 | products: [ 9 | .executable(name: "codeedit", targets: ["CodeEditCLI"]) 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0") 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "CodeEditCLI", 17 | dependencies: [ 18 | .product(name: "ArgumentParser", package: "swift-argument-parser") 19 | ]) 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

CodeEdit CLI

4 |

5 | 6 |

7 | 8 | 9 | 10 | 11 | 12 | 13 |

14 | 15 | `codeedit` is a set of command line tools that ship with CodeEdit which allow users to open and interact with editor via the command line. 16 | 17 | ## Installation 18 | 19 | ### Homebrew (Recommended) 20 | 21 | ```sh 22 | brew install codeeditapp/formulae/codeedit-cli 23 | 24 | # or 25 | 26 | brew tap codeeditapp/formulae 27 | brew install codeedit-cli 28 | ``` 29 | 30 | ### Download 31 | 32 | Download the universal binary from the latest release, extract the zip and move it to `/usr/local/bin/`. 33 | 34 | ### Build locally 35 | 36 | ```sh 37 | swift build -c release --arch arm64 --arch x86_64 38 | sudo cp -f .build/apple/Products/Release/codeedit /usr/local/bin/codeedit 39 | ``` 40 | 41 | > Note that you must have `CodeEdit` installed in a `Release` configuration. A `Debug` build of `CodeEdit` will not work. 42 | 43 | ## Documentation 44 | 45 | ### `open` 46 | 47 | Opens CodeEdit. 48 | 49 | ### Folder 50 | 51 | ```sh 52 | codeedit ./my-project 53 | ``` 54 | 55 | ### File 56 | 57 | ```sh 58 | codeedit index.html 59 | ``` 60 | 61 | From an optional line 62 | 63 | ```sh 64 | codeedit index.html:50 65 | ``` 66 | 67 | From an optional line and column 68 | 69 | ```sh 70 | codeedit index.html:50:50 71 | ``` 72 | 73 | ### `version` 74 | 75 | Outputs the version of CodeEdit and CodeEdit CLI Tools. 76 | 77 | ```sh 78 | codeedit version 79 | ``` 80 | 81 | ### `new-window` (not available yet) 82 | 83 | Opens a new window. 84 | 85 | ```sh 86 | codeedit new-window 87 | ``` 88 | 89 | ### `--goto` (not available yet) 90 | 91 | Opens a file at a line and optional position. 92 | 93 | Documentation coming soon. 94 | 95 | ### `list-extensions` (not available yet) 96 | 97 | Documentation coming soon. 98 | 99 | ### `install` (not available yet) 100 | 101 | Install an extension. 102 | 103 | Documentation coming soon. 104 | 105 | ### `uninstall` (not available yet) 106 | 107 | Uninstall an extension. 108 | 109 | Documentation coming soon. 110 | 111 | ### `activate` (not available yet) 112 | 113 | Activate an extension. 114 | 115 | Documentation coming soon. 116 | 117 | ### `deactivate` (not available yet) 118 | 119 | Deactivate an extension. 120 | 121 | Documentation coming soon. 122 | 123 | 124 | ## Related Repositories 125 | 126 | 127 | 128 | 134 | 140 | 146 | 152 | 158 | 159 |
129 | 130 | 131 |

        CodeEdit        

132 |
133 |
135 | 136 | 137 |

CodeEditSourceEditor

138 |
139 |
141 | 142 | 143 |

CodeEditTextView

144 |
145 |
147 | 148 | 149 |

CodeEditLanguages

150 |
151 |
153 | 154 | 155 |

     CodeEditKit     

156 |
157 |
160 | -------------------------------------------------------------------------------- /Sources/CodeEditCLI/Open.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Open.swift 3 | // CodeEditCLI 4 | // 5 | // Created by Lukas Pistrol on 06.12.22. 6 | // 7 | 8 | import ArgumentParser 9 | import Foundation 10 | 11 | extension CodeEditCLI { 12 | struct Open: ParsableCommand { 13 | static var configuration = CommandConfiguration( 14 | commandName: "open", 15 | abstract: "A command-line tool to open files/folders in CodeEdit.app." 16 | ) 17 | 18 | @Argument( 19 | help: """ 20 | The path of a file/folder to open. 21 | When opening files, line and column numbers can be appended: `index.html:42:10` 22 | """, 23 | completion: .file() 24 | ) 25 | private var path: String? 26 | 27 | func run() throws { 28 | let task = Process() 29 | let fileManager = FileManager.default 30 | 31 | // use the `open` cli as the executable 32 | task.launchPath = "/usr/bin/open" 33 | 34 | if let path = path { 35 | let (filePath, line, column) = try extractLineColumn(path) 36 | let openURL = try absolutePath(filePath, for: task) 37 | 38 | // Create directories if they don't exist 39 | let directoryURL = openURL.deletingLastPathComponent() 40 | do { 41 | try fileManager.createDirectory( 42 | at: directoryURL, 43 | withIntermediateDirectories: true, 44 | attributes: nil 45 | ) 46 | } catch { 47 | print("Failed to create directory at \(directoryURL.path): \(error)") 48 | return 49 | } 50 | 51 | if fileManager.fileExists(atPath: openURL.path) { 52 | // File exists, proceed to open it 53 | if let line = line, !openURL.hasDirectoryPath { 54 | task.arguments = ["-u", "codeedit://\(openURL.path):\(line):\(column ?? 1)"] 55 | } else { 56 | task.arguments = ["-u", "codeedit://\(openURL.path)"] 57 | } 58 | } else { 59 | // File doesn't exist, create one 60 | let success = fileManager.createFile(atPath: openURL.path, contents: nil, attributes: nil) 61 | if success { 62 | // Proceed to open the newly created file 63 | task.arguments = ["-u", "codeedit://\(openURL.path)"] 64 | } else { 65 | // Handle error if file creation fails 66 | print("Failed to create file at \(openURL.path)") 67 | return 68 | } 69 | } 70 | } else { 71 | task.arguments = ["-a", "CodeEdit.app"] 72 | } 73 | 74 | try task.run() 75 | } 76 | 77 | private func absolutePath(_ path: String, for task: Process) throws -> URL { 78 | guard let workingDirectory = task.currentDirectoryURL, 79 | let url = URL(string: path, relativeTo: workingDirectory) else { 80 | throw CLIError.invalidWorkingDirectory 81 | } 82 | return url 83 | } 84 | 85 | private func extractLineColumn(_ path: String) throws -> (path: String, line: Int?, column: Int?) { 86 | 87 | // split the string at `:` to get line and column numbers 88 | let components = path.split(separator: ":") 89 | 90 | // set path to only the first component 91 | guard let first = components.first else { 92 | throw CLIError.invalidFileURL 93 | } 94 | let path = String(first) 95 | 96 | // switch on the number of components 97 | switch components.count { 98 | case 1: // no line or column number provided 99 | return (path, nil, nil) 100 | 101 | case 2: // only line number provided 102 | guard let row = Int(components[1]) else { throw CLIError.invalidFileURL } 103 | return (path, row, nil) 104 | 105 | case 3: // line and column number provided 106 | guard let row = Int(components[1]), 107 | let column = Int(components[2]) else { throw CLIError.invalidFileURL } 108 | return (path, row, column) 109 | 110 | default: // any other case throw an error since this is invalid 111 | throw CLIError.invalidFileURL 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Sources/CodeEditCLI/Version.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Version.swift 3 | // CodeEditCLI 4 | // 5 | // Created by Lukas Pistrol on 06.12.22. 6 | // 7 | 8 | import ArgumentParser 9 | import AppKit 10 | 11 | extension CodeEditCLI { 12 | struct Version: ParsableCommand { 13 | static var configuration = CommandConfiguration( 14 | commandName: "version", 15 | abstract: "Prints the version of the CLI and CodeEdit.app." 16 | ) 17 | 18 | @Flag(name: .shortAndLong, help: "Only prints the version number of the CLI") 19 | var terse = false 20 | 21 | func run() throws { 22 | // if terse flag is set only print the cli version number 23 | if terse { 24 | print(CLI_VERSION) 25 | return 26 | } 27 | 28 | // Print the cli version 29 | print("CodeEditCLI: \t\(CLI_VERSION)") 30 | 31 | // File URL of CodeEdit.app 32 | let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "app.codeedit.CodeEdit") 33 | 34 | // Check if there is an Info.plist inside CodeEdit.app 35 | // Then get the version number and print it out 36 | // 37 | // This will fail when CodeEdit.app is not installed 38 | if let url = infoPlistUrl(appURL), 39 | let plist = NSDictionary(contentsOf: url) as? [String: Any], 40 | let version = plist["CFBundleShortVersionString"] as? String { 41 | print("CodeEdit.app: \t\(version)") 42 | } else { 43 | print("CodeEdit.app is not installed.") 44 | } 45 | } 46 | 47 | private func infoPlistUrl(_ url: URL?) -> URL? { 48 | if let url = url?.appendingPathComponent("Contents") 49 | .appendingPathComponent("Info.plist") { 50 | return url 51 | } else { 52 | return nil 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/CodeEditCLI/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // CodeEditCLI 4 | // 5 | // Created by Lukas Pistrol on 06.12.22. 6 | // 7 | 8 | import ArgumentParser 9 | import Foundation 10 | 11 | // ################################################## 12 | // This needs to be changed prior to every release! 13 | // ################################################## 14 | let CLI_VERSION = "0.0.10" 15 | 16 | struct CodeEditCLI: ParsableCommand { 17 | static let configuration = CommandConfiguration( 18 | commandName: "codeedit-cli", 19 | abstract: """ 20 | A set of command line tools that ship with CodeEdit 21 | which allow users to open and interact with editor via the command line. 22 | 23 | Version: \(CLI_VERSION) 24 | """, 25 | subcommands: [Open.self, Version.self], 26 | defaultSubcommand: Open.self 27 | ) 28 | 29 | enum CLIError: Error { 30 | case invalidWorkingDirectory 31 | case invalidFileURL 32 | } 33 | } 34 | 35 | CodeEditCLI.main() 36 | --------------------------------------------------------------------------------