File: README.md
CodeEdit is a code editor built by the community, for the community, written entirely and unapologetically for macOS. Features include syntax highlighting, code completion, project find and replace, snippets, terminal, task running, debugging, git integration, code review, extensions, and more.
[!IMPORTANT]
CodeEdit is currently in development and not yet recommended for production use, however you can take part in shaping it's future by test-driving pre-release versions and submitting an issue to let us know what you think.
Table of Contents
Motivation
Most editors in use today rely on Electron or other cross-platform frameworks, limiting their ability to fully utilize system resources. While Xcode provides a native experience, it is specifically designed for projects targeting Apple platforms.
We think developers working on projects not written for Apple platforms deserve that same macOS-native experience we get with Xcode while unlocking the full potential of the Mac.
This raised the question "what if such an editor existed?", a question that led to the creation of this concept, which our project aims to make a reality.
Mission
It is our commitment to keep CodeEdit open source and free forever, supported by the community.
Our goal is to maintain a lightweight experience, similar to TextEdit, while being able to scale up to a more feature-rich experience, comparable to Xcode, as necessary.
We strive to remain true to Apple's human interface guidelines and development patterns, ensuring CodeEdit looks and feels like an application developed by Apple themselves, which includes a meticulous attention to detail.
Community
Join our growing community on Discord and GitHub Discussions where we discuss and collaborate on all things CodeEdit. Don't be shy, jump right in and be part of the discussion!
[!NOTE]
We hold a weekly meetup on Discord every Saturday at 3pm UTC where we discuss latests development, feature requests, goals, and priorities.
Activity
Contributing
Be part of the next revolution in code editing by contributing to the project. This is a community-led effort, so we welcome as many contributors who can help. Read the Contribution Guide for more information.
This project spans multiple repositories so instead of browsing issues in the issues tab, it may be helpful to find an issue to get started on in our project board.
For issues we want to focus on that are most relevant at any given time, please see the issues scoped to our current iteration here.
Contributors
Sponsors
Support CodeEdit's development by becoming a sponsor.
Backers
Support CodeEdit's development by becoming a backer.
Thanks to all of our other backers
@ivanvorobei
@albertorestifo
@rkusa
@cadenkriese
@petrjahoda
@allejo
@frousselet
@wkillerud
License
Licensed under the MIT license.
Related Repositories
| CodeEditKit | CodeEditTextView | CodeEditSourceEditor | CodeEditLanguages | CodeEditCLI |
|---|
File: CODE_OF_CONDUCT.md
Contributor Covenant Code of Conduct
Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience - Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission - Repeated low-quality pull requests or pull requests not in accordance to project goals especially after warning from a project maintainer or administrator
- Other conduct which could reasonably be considered inappropriate in a
professional setting
Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
Github Issues.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
1. Correction
Community Impact: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
2. Warning
Community Impact: A violation through a single incident or series
of actions.
Consequence: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
3. Temporary Ban
Community Impact: A serious violation of community standards, including
sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
4. Permanent Ban
Community Impact: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within
the community.
Attribution
This Code of Conduct is adapted from the Contributor Covenant,
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by Mozilla's code of conduct
enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
File: CONTRIBUTING.md
Contribute to CodeEdit
Feel free to join and collaborate on our Discord Server.
[!WARNING]
Please do not submitlocalizationrelated pull requests at this time.
Once we are ready to support more languages we will let you know with a guide on how to contribute here and on our Discord Server.
Fork & Clone CodeEdit
Tap the "Fork" button on the top of the site. After forking clone the forked repository to your Mac.
Explore Issues
Find issues from the Issues tab or from the To Do column in our project. If you find an issue you want to work on, please indicate it in the issue and/or attach a draft PR once available. An admin or maintainer will then assign the Issue and/or PR to you.
[!IMPORTANT]
Please make sure to first comment under an issue or ask a maintainer to assign you to the issue before working on it. This helps prevent multiple people from working on the same
thing, which could result in your work not being merged. Additionally, some issues might be reserved for those with more in-depth knowledge of the codebase.
Getting Started
Please read the Getting Started guide in our wiki.
We also have a troubleshooting guide that provides common resolutions.
Code Style
Please read our guide on Code Style in our wiki.
Pull Request
Once you are happy with your changes, submit a Pull Request.
The pull request opens with a template loaded. Fill out all fields that are relevant.
The PR should include following information:
- A descriptive title on what changed.
- A detailed description of changes.
- If you made changes to the UI please add a screenshot or video as well.
- If there is a related issue please add a reference to the issue. If not, create one beforehand and link it.
- If your PR is still in progress mark it as Draft.
Checks, Tests & Documentation
Request a review from one of our admins @austincondiff, @lukepistrol, @MarcoCarnevali, @jasonplatts, @pkasila or maintainers @cstef, @linusS1, @RayZhao1998, @wdg.
[!TIP]
If it is your first PR, an admin will need to request a review for you.
[!IMPORTANT]
Please resolve allViolationerrors in Xcode (except: TODO: warnings). Otherwise the swiftlint check on GitHub will fail.
Once you submit the PR GitHub will run a couple of actions which run tests and SwiftLint (this can take a couple of minutes). Should a test fail, it cannot be merged until tests succeed.
Make sure to resolve all merge-conflicts otherwise the PR cannot be merged.
[!IMPORTANT]
Make sure your code is well documented so others can interact with your code easily!
File: LICENSE.md
MIT License
Copyright (c) 2022 CodeEdit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File: .swiftlint.yml
disabled_rules:
- todo
- trailing_comma
- nesting
type_name:
excluded:
- ID
identifier_name:
allowed_symbols: ["$", "_"]
excluded:
- id
- vc
paths to ignore during linting.
excluded:
- CodeEditModules/.build # Where Swift Package Manager checks out dependency sources
- DerivedData
opt_in_rules:
- attributes
- empty_count
- closure_spacing
- contains_over_first_not_nil
- missing_docs
- implicit_return
- modifier_order
- convenience_type
- pattern_matching_keywords
- multiline_parameters_brackets
- multiline_arguments_brackets
custom_rules:
spaces_over_tabs:
included: ".*\.swift"
name: "Spaces over Tabs"
regex: "\t"
message: "Prefer spaces for indents over tabs. See Xcode setting: 'Text Editing' -> 'Indentation'"
severity: warning
File: AppCast/_config.yml
Welcome to Jekyll!
This config file is meant for settings that affect your whole blog, values
which you are expected to set up once and rarely edit after that. If you find
yourself editing this file very often, consider using Jekyll's data files
feature for the data you need to update frequently.
For technical reasons, this file is *NOT* reloaded automatically when you use
'bundle exec jekyll serve'. If you change this file, please restart the server process.
Site settings
These are used to personalize your new site. If you look in the HTML files,
you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
You can create any custom variable you would like, and they will be accessible
in the templates via {{ site.myvariable }}.
title: CodeEdit.app
baseurl: "" # the subpath of your site, e.g. /blog
url: "" # the base hostname & protocol for your site, e.g. http://example.com
Build settings
markdown: kramdown
plugins:
- "jekyll-github-metadata"
Exclude from processing.
The following items will not be processed, by default. Create a custom list
to override the default setting.
exclude:
- Gemfile
- Gemfile.lock
- node_modules
- vendor/bundle/
- vendor/cache/
- vendor/gems/
- vendor/ruby/
File: CodeEdit/Assets.xcassets/Contents.json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/AccentColor.colorset/Contents.json
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Custom Colors/Contents.json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Custom Colors/Amber.colorset/Contents.json
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.133",
"green" : "0.635",
"red" : "0.784"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.302",
"green" : "0.812",
"red" : "0.961"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Custom Colors/CoolGray.colorset/Contents.json
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.553",
"green" : "0.541",
"red" : "0.518"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.549",
"green" : "0.537",
"red" : "0.525"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Custom Colors/FolderBlue.colorset/Contents.json
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.973",
"green" : "0.698",
"red" : "0.125"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.859",
"green" : "0.698",
"red" : "0.365"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Custom Colors/InspectorBackgroundColor.colorset/Contents.json
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF7",
"green" : "0xF5",
"red" : "0xF4"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x25",
"green" : "0x1F",
"red" : "0x1C"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Custom Colors/Scarlet.colorset/Contents.json
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.176",
"green" : "0.303",
"red" : "0.956"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.376",
"green" : "0.475",
"red" : "0.960"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Custom Colors/Steel.colorset/Contents.json
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.522",
"green" : "0.463",
"red" : "0.373"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.749",
"green" : "0.690",
"red" : "0.585"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Icons/Contents.json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Icons/BitBucketIcon.imageset/Contents.json
{
"images" : [
{
"filename" : "BitBucketIcon.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Icons/GitHubIcon.imageset/Contents.json
{
"images" : [
{
"filename" : "GitHubIcon.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GitHubIcon-Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/Icons/GitLabIcon.imageset/Contents.json
{
"images" : [
{
"filename" : "GitLabIcon.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GitLabIcon-Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEdit/Assets.xcassets/line.3.horizontal.decrease.chevron.filled.imageset/Contents.json
{
"images" : [
{
"filename" : "line.3.horizontal.decrease.chevron.filled.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
File: CodeEdit/Assets.xcassets/line.3.horizontal.decrease.chevron.imageset/Contents.json
{
"images" : [
{
"filename" : "line.3.horizontal.decrease.chevron.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
File: CodeEdit/Features/Keybindings/default_keybindings.json
[
{
"id": "copy", "name": "copy", "description": "Copy command", "context": "Global", "modifier": "command", "keybinding": "c"
},
{
"id": "paste", "name": "paste", "description": "Copy command", "context": "Global", "modifier": "command", "keybinding": "c"
}
]
File: CodeEdit/Preview Content/Preview Assets.xcassets/Contents.json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: CodeEditUITests/UI TESTING.md
UI Testing in CodeEdit
CodeEdit uses XCUITests for automating tests that require user interaction. Ideally, we have UI tests for every UI
component in CodeEdit, but right now (as of Jan, 2025) we have fewer tests than we'd like.
Test Application Setup
To test workspaces with real files, launch the application with the App enum. To create a temporary test directory,
use the App.launchWithTempDir() method. This will create a random directory in the temporary directory and return
the created path. In tests you can add files to that directory and it will be cleaned up when the tests finish.
There is a App.launchWithCodeEditWorkspace method, but please try not to use it. It exists for compatibility with a
few existing tests and would be a pain to replace. It's more likely to be flaky, and can't test things like file
modification, creation, or anything besides clicking around the workspace or you risk modifying the very project
that's being tested!
Query Extensions
For common, long, queries, add static methods to the Query enum. This enum should be used to help clarify tests
without having to read long XCUI queries. For instance
let window = application.windows.element(matching: .window, identifier: "workspace")
let navigator = window.descendants(matching: .any).matching(identifier: "ProjectNavigator").element
let newFileCell = navigator
.descendants(matching: .outlineRow)
.containing(.textField, identifier: "ProjectNavigatorTableViewCell-FileName")
.element(boundBy: 0)Should be shortened to the following, which should be easier to read and intuit what the test is doing.
let window = Query.getWindow(app)
let navigator = Query.Window.getProjectNavigator(window)
let newFileCell = Query.Navigator.getProjectNavigatorRow(fileTitle: "FileName", navigator)This isn't necessary for all tests, but useful for querying common regions like the project navigator, window, or
utility area.
File: Documentation.docc/Documentation.md
``CodeEdit``
Topics
About Window
Settings
Settings
App Window
CodeEditExtension
ExtensionManagerFolderMonitor
CodeEditUI
CodeFile
CodeFileDocumentCodeFileError
CommandPalette
CommandPaletteViewCommandPaletteViewModel
Documents
- doc:FileManagement
WorkspaceDocumentCEWorkspaceFileCEWorkspaceFileManagerCodeFileDocumentCodeEditDocumentController
Feedback
FeedbackViewFeedbackWindowControllerFeedbackToolbarFeedbackModelFeedbackTypeFeedbackIssueArea
Git
Keybindings
KeybindingManagerCommandManagerCommandCommandClosureWrapperKeyboardShortcutWrapper
LanguageServerProtocol
LSPClient
OpenQuickly
OpenQuicklyViewOpenQuicklyListItemViewOpenQuicklyViewModelOpenQuicklyPreviewView
Search
SearchModeModelSearchResultModelSearchResultMatchModelSearchResultLabel
Utils
CodeEditKeychainShellClientFileIcon
Welcome
File: Documentation.docc/About/About Window.md
About Window
The About Window displays general information about the app and acknowledgements to external dependencies.
Topics
About View
AboutViewAboutWindow
Acknowledgements
AcknowledgementsViewAcknowledgementsViewModelAcknowledgementsViewWindowControllerAcknowledgementRowViewAcknowledgementObjectAcknowledgementDependencyAcknowledgementPackageStateAcknowledgementPin
File: Documentation.docc/App Window/Adding New Tab Type.md
Adding a New Tab Type
This article is about how to add a new tab type to TabBar
Overview
First of all, each data type to be represented as tab in the UI should conform toEditorTabRepresentable protocol. For example, this is how it is done forFileItem:
final class FileItem: Identifiable, Codable, EditorTabRepresentable {
public var tabID: EditorTabID {
.codeEditor(id)
}
public var title: String {
self.url.lastPathComponent
}
public var icon: Image {
Image(systemName: self.systemImage)
}
public var iconColor: Color {
...
}
...
}Add new item identifier case
Each new tab type must have new identifier case, for example:
public enum EditorTabID: Codable, Identifiable, Hashable {
public var id: String {
switch self {
...
case .gitHistory(let repo):
return "gitHistory_\(repo)"
}
}
/// Represents Git history
case gitHistory(String)
}Opening and closing new tab types
Tabs are opened using WorkspaceDocument/openTab(item:) method. It does a set of common
things for all tabs. But also it calls a private method based on the EditorTabID of the
item. The private method for your EditorTabRepresentable MUST persist this item
somewhere (I recommend persisting them in WorkspaceSelectionState).
The same is for closing tabs using WorkspaceDocument/closeTab(item:) method.
Closing multiple tabs at once is handled by common functions, so there are no changes
required for them.
WorkspaceDocument/close() calls WorkspaceDocument.saveSelectionState() to persist Workspace Selection State to UserDefaults.
WorkspaceDocument/read(from:ofType:) calls WorkspaceDocument.readSelectionState() to retrieve Workspace Selection State from UserDefaults.
Also, because previously opened tabs are persisted in UserDefaults,
they should be recovered some way later. To recover new tab types you need to add
a case for WorkspaceDocument/read(from:ofType:) to let it know how to recover your new tab type.
If you need to persist something as code editor tabs do for files, then you need to add
functionality to persist changes to WorkspaceDocument/close().
Also, you need to add a case for new tab type toWorkspaceSelectionState/getItemByTab(id:). It will allow to useWorkspaceSelectionState/selected property and other features.
Adding a view for the new tab type
To add a view for new tab type, you need to add a case for your tab type toWorkspaceView/tabContent:
@ViewBuilder var tabContent: some View {
if let tabID = workspace.selectionState.selectedId {
switch tabID {
...
case .gitHistory:
GitHistoryView(windowController: windowController, workspace: workspace)
}
} else {
noEditor
}
}File: Documentation.docc/App Window/App Window.md
App Window
A collection of all the views that make up the main app window.
Topics
Window Controller
CodeEditWindowControllerWorkspaceView
Main Content
EditorAreaViewEditorAreaFileViewCodeFileViewNonTextFileViewAnyFileViewLoadingFileViewImageFileViewPDFFileView
JumpBar
JumpBarViewJumpBarComponentJumpBarMenuJumpBarMenuItem
Navigator Sidebar
NavigatorSidebarView
Inspector Sidebar
InspectorAreaView
Status Bar
StatusBarView
Tab Bar
TabBarView
Terminal Emulator
TerminalEmulatorView
File: Documentation.docc/App Window/InspectorSidebarView.md
``CodeEdit/InspectorAreaView``
Topics
Toolbars
InspectorAreaToolbarTop
File Inspector
FileInspectorViewFileInspectorModelFileLocationIndentUsingLanguageTypeLineEndingsTextEncoding
History Inspector
HistoryInspectorViewHistoryInspectorModelHistoryInspectorItemViewHistoryInspectorNoHistoryViewHistoryPopoverView
Quick Help Inspector
QuickHelpInspectorView
No Selection
NoSelectionInspectorView
File List
FileTypeList
File: Documentation.docc/App Window/NavigatorSidebarView.md
``CodeEdit/NavigatorSidebarView``
Topics
Toolbars
NavigatorSidebarToolbarTopNavigatorSidebarToolbarBottom
Project Navigator
ProjectNavigatorViewOutlineViewOutlineViewControllerOutlineMenuOutlineTableViewCellOutlineTableViewCellDelegate
Source Control Navigator
SourceControlNavigatorViewSourceControlModelSourceControlSearchToolbarSourceControlToolbarBottomSourceControlNavigatorRepositoriesViewSourceControlNavigatorChangesViewSourceControlNavigatorChangedFileView
Find Navigator
FindNavigatorViewFindNavigatorSearchBarFindNavigatorModeSelectorFindNavigatorResultListFindNavigatorListViewControllerFindNavigatorListMatchCell
Extension Navigator
ExtensionNavigatorViewExtensionNavigatorItemViewExtensionNavigatorDataExtensionInstallationViewExtensionInstallationViewModel
File: Documentation.docc/App Window/StatusBarView.md
``CodeEdit/StatusBarView``
Topics
Model
ImageDimensions
View Model
StatusBarViewModel
View Modifiers
UpdateStatusBarInfo
Items
StatusBarMenuStyleStatusBarBreakpointButtonStatusBarIndentSelectorStatusBarEncodingSelectorStatusBarLineEndSelectorStatusBarToggleUtilityAreaButtonStatusBarCursorPositionLabel
File: Documentation.docc/App Window/TabBarView.md
``CodeEdit/TabBarView``
Topics
Articles
Model
EditorTabIDEditorTabRepresentable
Components
EditorTabViewTabBarContextMenuEditorTabButtonStyleTabDividerTabBarTopDividerTabBarBottomDividerTabBarAccessoryIconTabBarXcodeBackground
File: Documentation.docc/App Window/UtilityAreaView.md
``CodeEdit/UtilityAreaView``
Topics
Model
UtilityAreaTab
View Model
UtilityAreaViewModelUtilityAreaTabViewModel
Utility
UtilityAreaTerminalUtilityAreaTerminalTabUtilityAreaDebugViewUtilityAreaOutputView
Toolbar
UtilityAreaViewUtilityAreaSplitTerminalButtonUtilityAreaMaximizeButtonUtilityAreaClearButtonUtilityAreaFilterTextField
File: Documentation.docc/AppPreferences/AppPreferences.md
``CodeEdit/Settings``
Topics
Getting Started
Settings Model
SettingsModelSoftwareUpdater
Settings Section Views
GeneralSettingsViewThemeSettingsViewTextEditingSettingsViewTerminalSettingsViewLocationsSettingsViewKeybindingsSettingsViewAccountSettingsViewSourceControlSettingsViewSettingsPlaceholderView
Helper Views
SettingsContentSettingsSectionSettingsColorPickerSettingsToolbar
Theme Settings Model
ThemeThemeModel
File: Documentation.docc/AppPreferences/Create a View.md
Create a View
Now that you followed the doc:Getting-Started guide it's time to create a view.
Add setting to existing Section
In our example we added ourNewOption in Settings/GeneralSettings.
Now let's take a look at the GeneralSettingsView.
import SwiftUI
struct GeneralSettingsView: View {
@AppSettings(\.general)
var settings
var body: some View {
SettingsForm {
Section {
appearance
fileIconStyle
navigatorTabBarPosition
inspectorTabBarPosition
...
}
}
}
}As you can see SettingsModel is already setup and ready to use.
To add your option toggle below the other options just add something like this:
private extension GeneralSettingsView {
// MARK: - Settings View
private var yourOption: some View {
Toggle("Your text", isOn: $general.yourNewOption)
}
}Then add it to var body: some View
struct GeneralSettingsView: View {
var body: some View {
SettingsForm {
Section {
appearanceSection
showIssuesSection
fileExtensionsSection
// REMOVEME: et cetera
yourOptionSection
}
}
}
}And now you're done!
Implement a new section
Tip: Rename YourSection to the section name that you want
To implement a new section first create a new folder inside the Pages folder and name it accordingly.
Inside the folder create a new SwiftUI view and name it "YourSectionSettingsView.swift".
Then create a new folder inside called Models and inside of it create a file named "YourSectionSettings.swift"
Tip: The order that pages are arranged in the array is the same as in the settings window, the first array member will be the top item
Then find the file `SettingsPage.swift` and add `YourSection` to the `enum Name` like this:
```swift
enum Name: String {
case general = "General"
case advanced = "Advanced"
// et cetera
case yourSection = "YourSection"
}Back in YourSectionView.swift implement your option like this:
import SwiftUI
struct YourSectionSettingsView: View {
@AppSettings(\.yourSection)
var yourSection
var body: some View {
SettingsForm {
Section {
yourToggleSection
}
}
}
}
private extension YourSectionSettingsView {
// MARK: - Settings Views
private var yourToggle: some View {
Toggle("Your option", isOn: $yourSection.yourNewOption)
}
}There are 3 more steps, almost done.
Open ModelNameToSettingName.swift and add your translated search result:
let translator: [String: String] = [
// MARK: - General Settings
"appAppearance": NSLocalizedString("Appearance", comment: ""),
"fileIconStyle": NSLocalizedString("File Icon Style", comment: ""),
// etc
// MARK: - Your Section
"yourOption": NSLocalizedString("Your Option", comment: "Your translation comment")
]Now, open SettingsView.swift and add your section to the populatePages() method:
/// Creates all the neccessary pages
private func populatePages() -> [SettingsPage] {
var pages = [SettingsPage]()
let settingsData = SettingsData()
let generalSettings = SettingsPage(.general, baseColor: .gray, icon: .system("gear"))
pages = createPageAndSettings(settingsData.general, parent: generalSettings, prePages: pages)
let accountsSettings = SettingsPage(.accounts, baseColor: .blue, icon: .system("at"))
pages = createPageAndSettings(settingsData.accounts, parent: accountsSettings, prePages: pages)
// etc
let yourSectionSettings = SettingsPage(.yourSection, baseColor: /* add color here */, icon: /* add icon */)
pages = createPageAndSettings(settingsData.yourSection, parent: yourSectionSettings, prePages: pages)
return pages
}When you are done, add YourSectionSettingsView to SettingsView.swift:
Group {
switch selectedPage {
case .general:
GeneralSettingsView().environmentObject(updater)
case .yourSection:
YourSectionSettingsView()
default:
Text("Implementation Needed").frame(alignment: .center)
}
}File: Documentation.docc/AppPreferences/Getting Started.md
Getting Started
There are a few things to consider when using the Settings.
Reading/Writing Values
The Settings can be accessed from everywhere in the app like this:
@AppSettings(\.settingName)
var settingToggle("Enable some Feature", value: $setting)Creating a New Preference
When implementing a new feature, we might have some options in regards to this new feature we want to show the user in the apps Settings Window.
Find a Section
The settings window is structured in different sections. Figure out in which section your new option should appear in.
If the section is already populated with other options (e.g. Settings/GeneralSettings), just add your new option like this:
struct GeneralSettings: Codable, Hashable {
// ...
// This will be your new option. Be sure to provide a default value
public var yourNewOption: Bool = true
public init() {}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// ...
// Try to decode the value from json.
self.yourNewOption = try container.decodeIfPresent(
Bool.self,
forKey: .yourNewOption
) ?? true // If the key is not present in the json, set the default value
}
}Create a Section
In some cases in early development the section you decided on where to put your option in might not yet have been implemented. In this
case you can create a new struct inside Settings like this:
public extension YourNewSection: Codable {
// This will be your new option. Be sure to provide a default value
public var yourNewOption: Bool = true
public init() {}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Try to decode the value from json.
self.yourNewOption = try container.decodeIfPresent(
Bool.self,
forKey: .yourNewOption
) ?? true // If the key is not present in the json, set the default value
}
}Now let's add the new section to Settings like this:
public struct Settings: Codable {
// ...
// Add your new section above the `public init() {}`
public var yourNewSection: YourNewSection = .init()
// ...
}Topics
Up Next
Main Components
SettingsSettingsModel
File: Documentation.docc/AppPreferences/Themes.md
``CodeEdit/Theme``
Overview
A Theme is stored in a theme_name.json file in the ~/Library/Application Support/CodeEdit/themes/ directory. There are a
couple of bundled themes that will automatically be put there once the app starts.
Once a JSON file is loaded, the Theme gets added to ThemeModel/themes.
They can either be Theme/ThemeType/dark or Theme/ThemeType/light.
JSON Structure
{
"author" : "CodeEdit",
"name" : "codeedit-xcode-dark",
"displayName" : "Xcode Dark",
"description" : "Xcode dark theme.",
"version" : "0.0.1",
"license" : "MIT",
"type" : "dark",
"distributionURL" : "https:\/\/github.com\/CodeEditApp\/CodeEdit",
"editor" : { ... },
"terminal" : { ... }
}| Key | Description |
|---|---|
author |
Your Name |
name |
A unique string representing the theme. It's good practice to start it with your name or domain to make sure it is unique. |
displayName |
The name that will appear in the UI |
description / metadataDescription |
A short description that will appear when hovering over the theme thumbnail |
version |
A version number |
license |
Which license your theme is published under |
type / appearance |
The type of the theme [dark, light] |
distributionURL |
A URL to your web presentation |
editor |
A collection of colors for the editor |
terminal |
A collection of colors for the terminal |
Editor
{
"invisibles" : {
"color" : "#424D5B"
},
"comments" : {
"color" : "#73A74E"
},
"numbers" : {
"color" : "#D0BF69"
},
"commands" : {
"color" : "#67B7A4"
},
"lineHighlight" : {
"color" : "#23252B"
},
"values" : {
"color" : "#A167E6"
},
"background" : {
"color" : "#1F1F24"
},
"keywords" : {
"color" : "#FF7AB3"
},
"text" : {
"color" : "#D9D9D9"
},
"insertionPoint" : {
"color" : "#D9D9D9"
},
"strings" : {
"color" : "#FC6A5D"
},
"selection" : {
"color" : "#515B70"
},
"types" : {
"color" : "#5DD8FF"
},
"variables" : {
"color" : "#41A1C0"
},
"attributes" : {
"color" : "#D0A8FF"
},
"characters" : {
"color" : "#D0BF69"
}
}Terminal
{
"white" : {
"color" : "#d9d9d9"
},
"brightMagenta" : {
"color" : "#af52de"
},
"brightRed" : {
"color" : "#ff3b30"
},
"blue" : {
"color" : "#007aff"
},
"red" : {
"color" : "#ff3b30"
},
"green" : {
"color" : "#28cd41"
},
"boldText" : {
"color" : "#d9d9d9"
},
"brightGreen" : {
"color" : "#28cd41"
},
"background" : {
"color" : "#1f2024"
},
"cursor" : {
"color" : "#d9d9d9"
},
"selection" : {
"color" : "#515b70"
},
"magenta" : {
"color" : "#af52de"
},
"black" : {
"color" : "#1f2024"
},
"text" : {
"color" : "#d9d9d9"
},
"brightWhite" : {
"color" : "#ffffff"
},
"brightBlue" : {
"color" : "#007aff"
},
"brightYellow" : {
"color" : "#ffff00"
},
"cyan" : {
"color" : "#59adc4"
},
"yellow" : {
"color" : "#ffcc00"
},
"brightCyan" : {
"color" : "#55bef0"
},
"brightBlack" : {
"color" : "#8e8e93"
}
}Topics
General Info
authornamedisplayNamemetadataDescriptionversionlicenseappearancedistributionURLThemeType
Editor
Theme/EditorColorseditorAttributes
Terminal
Theme/TerminalColorsterminalAttributes
File: Documentation.docc/AppPreferences/Sections/AccountPreferencesView.md
``CodeEdit/AccountSettingsView``
Topics
Model
Settings/AccountsSettingsSourceControlAccountsSourceControlProvider
Views
AccountListItemViewAccountSelectionDialogGitAccountItemGitAccountItemView
Login Views
GitLabHostedLoginViewGitLabLoginViewGitHubLoginViewGitHubEnterpriseLoginView
File: Documentation.docc/AppPreferences/Sections/GeneralPreferencesView.md
``CodeEdit/GeneralSettingsView``
Topics
Model
Settings/GeneralSettings
File: Documentation.docc/AppPreferences/Sections/KeybindingsPreferencesView.md
``CodeEdit/KeybindingsSettingsView``
Topics
Model
Settings/KeybindingsSettings
File: Documentation.docc/AppPreferences/Sections/SourceControlPreferencesView.md
``CodeEdit/SourceControlSettingsView``
Topics
Model
Settings/SourceControlSettingsIgnoredFiles
Views
SourceControlGeneralViewSourceControlGitViewIgnoredFileView
File: Documentation.docc/AppPreferences/Sections/TerminalPreferencesView.md
``CodeEdit/TerminalSettingsView``
Topics
Model
Settings/TerminalSettings
File: Documentation.docc/AppPreferences/Sections/TextEditingPreferencesView.md
``CodeEdit/TextEditingSettingsView``
Topics
Model
Settings/TextEditingSettings
File: Documentation.docc/AppPreferences/Sections/ThemePreferencesView.md
``CodeEdit/ThemeSettingsView``
Topics
Model
Settings/ThemeSettingsThemeModel
Views
PreviewThemeViewTerminalThemeViewEditorThemeViewThemePreviewIcon
File: Documentation.docc/CodeEditUI/CodeEditUI.md
CodeEditUI
A collection of reusable UI elements for CodeEdit.
Overview
This module contains UI elements that can be reused throughout the CodeEdit app.
Topics
Controls
ToolbarBranchPickerHelpButtonSegmentedControlSettingsTextEditor
Other
EffectViewOverlayPanelPressActionsPanelDivider
File: Documentation.docc/CodeEditUI/HelpButton.md
``CodeEdit/HelpButton``
Usage
HelpButton {
// an action to perform on click
}Preview
File: Documentation.docc/CodeEditUI/SegmentedControl.md
``CodeEdit/SegmentedControl``
Usage
@State var selected: Int = 0
var items: [String] = ["Tab 1", "Tab 2"]
SegmentedControl($selected, options: items)Preview
Topics
Item
SegmentedControlItem
File: Documentation.docc/CodeEditUI/ToolbarBranchPicker.md
``CodeEdit/ToolbarBranchPicker``
Overview
When the current project is a git repository, this will show the currently
checked-out branch as a subtitle. Once a tap is registered, a popup will
appear displaying the currently checked-out branch and all other local branches.
This view should be set to the view property in a NSToolbarItem.
Usage
First make sure a WorkspaceDocument is accessible in the context.
var workspace: WorkspaceDocument?Then intoolbar(_:itemForItemIdentifier:willBeInsertedIntoToolbar:),
create a new NSToolbarItem:
let toolbarItem = NSToolbarItem(itemIdentifier: /* Identifier */)
// create a NSHostingView
let view = BranchPickerToolbarItem(workspace?.workspaceClient)
let hostingView = NSHostingView(rootView: view)
// set the view property of the toolbar item
toolbarItem.view = hostingView
// return the toolbar item
return toolbarItemPreview
File: Documentation.docc/FileManagement/FileManagement.md
File Management
Working with files and directories in CodeEdit.
Overview
CodeEdit manages files using three classes:
CEWorkspaceFilefor representing files and other file system objects.CEWorkspaceFileManagerfor loading, modifying, and listening to the file system.CodeFileDocumentfor loading contents of files for editing.
File: Documentation.docc/Git/Git.md
Git
Topics
Client
GitClient
Protocols
GitRouterGitJSONPostRouterGitRouterConfigurationGitURLSessionGitURLSessionDataTaskProtocol
Structs
GitCommitGitAccountItemGitChangedFileGitHTTPHeader
Enums
GitHTTPEncodingGitHTTPMethodGitTypeGitTimeGitURLGitSortDirectionGitSortType
Views
GitCheckoutBranchViewGitCloneViewGitHubEnterpriseLoginViewGitHubLoginViewGitLabHostedLoginViewGitLabLoginView
GitHub
GitHubFileGitHubGistGitHubIssueGitHubPullRequestGitHubRepositoriesGitHubUserGitHubAccountGitHubCommentGitHubReviewGitHubTokenConfigurationGitHubRouterGitHubUserRouterGitHubGistRouterGitHubIssueRouterGitHubOAuthRouterGitHubOAuthConfigurationGitHubOpennessGitHubPreviewHeaderGitHubPublicKeyRouterGitHubPullRequestRouterGitHubRepositoryRouterGitHubReviewsRouter
GitLab
GitLabAvatarURLGitLabCommitGitLabCommitCommentGitLabCommitDiffGitLabCommitStatsGitLabCommitStatusGitLabEventGitLabEventDataGitLabGroupAccessGitLabPermissionsGitLabProjectGitLabProjectAccessGitLabProjectHookGitLabUserGitLabNamespaceGitLabEventNoteGitLabAccountGitLabTokenConfigurationGitLabOAuthConfigurationGitLabPrivateTokenConfigurationGitLabSortGitLabOrderByGitLabVisibilityGitLabVisibilityLevelGitLabUserRouterGitLabCommitRouterGitLabProjectRouterGitLabOAuthRouter
BitBucket
BitBucketEmailBitBucketRepositoriesBitBucketUserBitBucketAccountBitBucketOAuthConfigurationBitBucketTokenConfigurationBitBucketOAuthRouterBitBucketRepositoryRouterBitBucketTokenRouterBitBucketUserRouterBitbucketPaginatedResponse
File: Documentation.docc/Keybindings/KeybindingManager.md
``CodeEdit/KeybindingManager``
This module created in order to put all keybindings into single place in code, so it'd be easy to interact, reuse and change keybindings without going through every class and changing code to use other shortcut. It uses default_keybindings.json file to store initial set of keybindings. After app launched all keybindings loaded into memory and can be referenced via KeybindingManager/named(with:) function.
Initial setup
In order to get it working you just need to add Keybindings as dependency to your module just like
.target(
name: "WelcomeModule",
dependencies: [
...other dependencies
"Keybindings",
])Keybinding module exists as singleton, so you always can reference Keybindings using KeybindingManager.shared
Topics
Fetching shortcut
In order to fetch keybinding you need to call following function with string name KeybindingManager/named(with:) returning you KeyboardShortcutWrapper which contains KeyboardShortcutWrapper/keyboardShortcut which can be passed directly to keyboardShortcut. So the end code would look like .keyboardShortcut(KeyboardShortcutWrapper.shared.named(with: "copy").keyboardShortcut
If shortcut wasn't found by name, it will return fallback shortcut which has following keybinding Shift + ?
Adding new shortcut
To add new shortcut you need first to add new row to default_keybindings.json file. Make sure you follow other keybindings format. Also check that there's no other keybindings with same ID,
because we use it to identify keybindings later. Once added - you can refer to Fetching Shortcut section. It is possible to add new shortcut in runtime via KeybindingManager/addNewShortcut(shortcut:name:)
File: Documentation.docc/KeyChain/CodeEditKeychain.md
``CodeEdit/CodeEditKeychain``
Topics
Articles
Enumerations
CodeEditKeychainAccessOptionsCodeEditKeychainConstants
File: Documentation.docc/KeyChain/What is Keychain.md
What is Keychain?
Keychain is the password management system in macOS, developed by Apple. It was introduced with Mac OS 8.6, and has been included in all subsequent versions of the operating system, now known as macOS. A Keychain can contain various types of data: passwords, private keys, certificates, and secure notes.
Notice:
This build of CodeEditKeychain could change at anytime if bugs or breaking changes are found in the module.
Usage
String
let keychain = CodeEditKeychain()
keychain.set("hello world", forKey: "my key")
keychain.get("my key")Boolean
let keychain = CodeEditKeychain()
keychain.set(true, forKey: "my key")
keychain.getBool("my key")Data
let keychain = CodeEditKeychain()
keychain.set(dataObject, forKey: "my key")
keychain.getData("my key")Removing Keys
let keychain = CodeEditKeychain()
keychain.delete("my key")Return All Keys
let keychain = CodeEditKeychain()
keychain.allKeys // Returns the names of all keysCheck if operation was successful
One can verify if set, delete and clear methods finished successfully by checking their return values. Those methods return true on success and false on error.
if keychain.set("hello world", forKey: "my key") {
// Keychain item is saved successfully
} else {
// Report error
}Setting key prefix
One can pass a keyPrefix argument when initializing a CodeEditKeychain object. The string passed in keyPrefix argument will be used as a prefix to all the keys used in set, get, getData and delete methods. Adding a prefix to the keychain keys can be useful in unit tests. This prevents the tests from changing the Keychain keys that are used when the app is launched manually.
let keychain = CodeEditKeychain(keyPrefix: "myTestKey_")
keychain.set("hello world", forKey: "hello") // Value will be stored under "myTestKey_hello" keyFile: Documentation.docc/Welcome/Welcome Window.md
Welcome Window
Topics
Views
WelcomeWindowViewWelcomeViewWelcomeActionViewRecentProjectsViewRecentProjectItem
File: OpenWithCodeEdit/Media.xcassets/Contents.json
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
File: ProductIcons/CodeEditAlphaIcon.icon/icon.json
{
"color-space-for-untagged-svg-colors" : "display-p3",
"fill" : {
"linear-gradient" : [
"display-p3:0.36667,0.77129,1.00000,1.00000",
"srgb:0.00000,0.42405,1.00000,1.00000"
]
},
"groups" : [
{
"blend-mode" : "normal",
"blur-material" : 0.05,
"hidden" : false,
"layers" : [
{
"glass" : true,
"hidden-specializations" : [
{
"value" : true
},
{
"appearance" : "dark",
"value" : false
},
{
"appearance" : "tinted",
"value" : true
}
],
"image-name" : "label-alpha-light.svg",
"name" : "label-alpha-light",
"opacity-specializations" : [
{
"value" : 1
},
{
"appearance" : "dark",
"value" : 1
},
{
"appearance" : "tinted",
"value" : 0
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
33.1290445202104,
205.5
]
}
},
{
"hidden-specializations" : [
{
"value" : false
},
{
"appearance" : "dark",
"value" : true
},
{
"appearance" : "tinted",
"value" : false
}
],
"image-name" : "label-alpha-dark.svg",
"name" : "label-alpha-dark",
"opacity-specializations" : [
{
"value" : 1
},
{
"appearance" : "dark",
"value" : 0
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
33.1290445202104,
205.5
]
}
}
],
"lighting" : "combined",
"name" : "Label",
"position" : {
"scale" : 1.25,
"translation-in-points" : [
0,
0
]
},
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : false,
"translucency" : {
"enabled" : true,
"value" : 0.5
}
},
{
"blur-material" : 0.5,
"hidden" : false,
"layers" : [
{
"hidden" : false,
"image-name" : "pen-dark-color.png",
"name" : "pen-dark-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light-color.png",
"name" : "pen-light-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 1
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light.png",
"name" : "pen-light",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 1
}
]
},
{
"glass" : true,
"hidden" : false,
"image-name" : "CodeEdit pen.png",
"name" : "CodeEdit pen",
"opacity-specializations" : [
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Pen",
"position" : {
"scale" : 0.88,
"translation-in-points" : [
0,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.75
},
"specular" : false,
"translucency" : {
"enabled" : true,
"value" : 0.25
}
},
{
"blend-mode" : "normal",
"blur-material" : null,
"hidden" : false,
"layers" : [
{
"fill-specializations" : [
{
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,1.00000"
}
},
{
"appearance" : "dark",
"value" : "automatic"
},
{
"appearance" : "tinted",
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,0.86284"
}
}
],
"glass" : true,
"hidden" : false,
"image-name" : "Slice 1 (9).svg",
"name" : "CodeLines",
"opacity-specializations" : [
{
"value" : 0.75
},
{
"appearance" : "dark",
"value" : 0.5
},
{
"appearance" : "tinted",
"value" : 0.33
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Code Lines",
"position" : {
"scale" : 1.24,
"translation-in-points" : [
-2.4609375,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
File: ProductIcons/CodeEditBetaIcon.icon/icon.json
{
"color-space-for-untagged-svg-colors" : "display-p3",
"fill" : {
"linear-gradient" : [
"display-p3:0.36667,0.77129,1.00000,1.00000",
"srgb:0.00000,0.42405,1.00000,1.00000"
]
},
"groups" : [
{
"blend-mode" : "normal",
"blur-material" : 0.05,
"hidden" : false,
"layers" : [
{
"hidden-specializations" : [
{
"value" : true
},
{
"appearance" : "dark",
"value" : false
},
{
"appearance" : "tinted",
"value" : true
}
],
"image-name" : "label-beta-light.svg",
"name" : "label-beta-light",
"position" : {
"scale" : 1,
"translation-in-points" : [
91.12904452021039,
205.5
]
}
},
{
"hidden-specializations" : [
{
"value" : false
},
{
"appearance" : "dark",
"value" : true
},
{
"appearance" : "tinted",
"value" : false
}
],
"image-name" : "label-beta-dark.svg",
"name" : "label-beta-dark",
"position" : {
"scale" : 1,
"translation-in-points" : [
91.12904452021039,
205.5
]
}
}
],
"lighting" : "combined",
"name" : "Label",
"position" : {
"scale" : 1.25,
"translation-in-points" : [
0,
0
]
},
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : false,
"translucency" : {
"enabled" : true,
"value" : 0.5
}
},
{
"blur-material" : 0.5,
"hidden" : false,
"layers" : [
{
"hidden" : false,
"image-name" : "pen-dark-color.png",
"name" : "pen-dark-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light-color.png",
"name" : "pen-light-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 1
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light.png",
"name" : "pen-light",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 1
}
]
},
{
"glass" : true,
"hidden" : false,
"image-name" : "CodeEdit pen.png",
"name" : "CodeEdit pen",
"opacity-specializations" : [
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Pen",
"position" : {
"scale" : 0.88,
"translation-in-points" : [
0,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.75
},
"specular" : false,
"translucency" : {
"enabled" : true,
"value" : 0.25
}
},
{
"blend-mode" : "normal",
"blur-material" : null,
"hidden" : false,
"layers" : [
{
"fill-specializations" : [
{
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,1.00000"
}
},
{
"appearance" : "dark",
"value" : "automatic"
},
{
"appearance" : "tinted",
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,0.86284"
}
}
],
"glass" : true,
"hidden" : false,
"image-name" : "Slice 1 (9).svg",
"name" : "CodeLines",
"opacity-specializations" : [
{
"value" : 0.75
},
{
"appearance" : "dark",
"value" : 0.5
},
{
"appearance" : "tinted",
"value" : 0.33
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Code Lines",
"position" : {
"scale" : 1.24,
"translation-in-points" : [
-2.4609375,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
File: ProductIcons/CodeEditDevIcon.icon/icon.json
{
"color-space-for-untagged-svg-colors" : "display-p3",
"fill" : {
"linear-gradient" : [
"display-p3:0.36667,0.77129,1.00000,1.00000",
"srgb:0.00000,0.42405,1.00000,1.00000"
]
},
"groups" : [
{
"blend-mode" : "normal",
"blur-material" : 0.05,
"hidden" : false,
"layers" : [
{
"blend-mode" : "normal",
"hidden-specializations" : [
{
"value" : true
},
{
"appearance" : "dark",
"value" : false
},
{
"appearance" : "tinted",
"value" : true
}
],
"image-name" : "label-dev-light.svg",
"name" : "label-dev-light",
"opacity" : 1,
"position" : {
"scale" : 1,
"translation-in-points" : [
122.12904452021026,
205.5
]
}
},
{
"hidden-specializations" : [
{
"value" : false
},
{
"appearance" : "dark",
"value" : true
},
{
"appearance" : "tinted",
"value" : false
}
],
"image-name" : "label-dev-dark.svg",
"name" : "label-dev-dark",
"position" : {
"scale" : 1,
"translation-in-points" : [
122.12904452021037,
205.5
]
}
}
],
"lighting" : "combined",
"name" : "Label",
"position" : {
"scale" : 1.25,
"translation-in-points" : [
0,
0
]
},
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : false,
"translucency" : {
"enabled" : true,
"value" : 0.5
}
},
{
"blur-material" : 0.5,
"hidden" : false,
"layers" : [
{
"hidden" : false,
"image-name" : "pen-dark-color.png",
"name" : "pen-dark-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light-color.png",
"name" : "pen-light-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 1
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light.png",
"name" : "pen-light",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 1
}
]
},
{
"glass" : true,
"hidden" : false,
"image-name" : "CodeEdit pen.png",
"name" : "CodeEdit pen",
"opacity-specializations" : [
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Pen",
"position" : {
"scale" : 0.88,
"translation-in-points" : [
0,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.75
},
"specular" : false,
"translucency" : {
"enabled" : true,
"value" : 0.25
}
},
{
"blend-mode" : "normal",
"blur-material" : null,
"hidden" : false,
"layers" : [
{
"fill-specializations" : [
{
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,1.00000"
}
},
{
"appearance" : "dark",
"value" : "automatic"
},
{
"appearance" : "tinted",
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,0.86284"
}
}
],
"glass" : true,
"hidden" : false,
"image-name" : "Slice 1 (9).svg",
"name" : "CodeLines",
"opacity-specializations" : [
{
"value" : 0.75
},
{
"appearance" : "dark",
"value" : 0.5
},
{
"appearance" : "tinted",
"value" : 0.33
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Code Lines",
"position" : {
"scale" : 1.24,
"translation-in-points" : [
-2.4609375,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
File: ProductIcons/CodeEditIcon.icon/icon.json
{
"color-space-for-untagged-svg-colors" : "display-p3",
"fill" : {
"linear-gradient" : [
"display-p3:0.36667,0.77129,1.00000,1.00000",
"srgb:0.00000,0.42405,1.00000,1.00000"
]
},
"groups" : [
{
"blur-material" : 0.5,
"hidden" : false,
"layers" : [
{
"hidden" : false,
"image-name" : "pen-dark-color.png",
"name" : "pen-dark-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light-color.png",
"name" : "pen-light-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 1
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light.png",
"name" : "pen-light",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 1
}
]
},
{
"glass" : true,
"hidden" : false,
"image-name" : "CodeEdit pen.png",
"name" : "CodeEdit pen",
"opacity-specializations" : [
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Pen",
"position" : {
"scale" : 0.88,
"translation-in-points" : [
0,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.75
},
"specular" : false,
"translucency" : {
"enabled" : true,
"value" : 0.25
}
},
{
"blend-mode" : "normal",
"blur-material" : null,
"hidden" : false,
"layers" : [
{
"fill-specializations" : [
{
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,1.00000"
}
},
{
"appearance" : "dark",
"value" : "automatic"
},
{
"appearance" : "tinted",
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,0.86284"
}
}
],
"glass" : true,
"hidden" : false,
"image-name" : "Slice 1 (9).svg",
"name" : "CodeLines",
"opacity-specializations" : [
{
"value" : 0.75
},
{
"appearance" : "dark",
"value" : 0.5
},
{
"appearance" : "tinted",
"value" : 0.33
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Code Lines",
"position" : {
"scale" : 1.24,
"translation-in-points" : [
-2.4609375,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
File: ProductIcons/CodeEditPreIcon.icon/icon.json
{
"color-space-for-untagged-svg-colors" : "display-p3",
"fill" : {
"linear-gradient" : [
"display-p3:0.36667,0.77129,1.00000,1.00000",
"srgb:0.00000,0.42405,1.00000,1.00000"
]
},
"groups" : [
{
"blend-mode" : "normal",
"blur-material" : 0.05,
"hidden" : false,
"layers" : [
{
"hidden-specializations" : [
{
"value" : true
},
{
"appearance" : "dark",
"value" : false
},
{
"appearance" : "tinted",
"value" : true
}
],
"image-name" : "label-pre-light.svg",
"name" : "label-pre-light",
"position" : {
"scale" : 1,
"translation-in-points" : [
129.62904452021024,
205.5
]
}
},
{
"blend-mode" : "screen",
"glass" : true,
"hidden-specializations" : [
{
"value" : false
},
{
"appearance" : "dark",
"value" : true
},
{
"appearance" : "tinted",
"value" : false
}
],
"image-name" : "label-pre-dark.svg",
"name" : "label-pre-dark",
"opacity" : 1,
"position" : {
"scale" : 1,
"translation-in-points" : [
129.62904452021024,
205.5
]
}
}
],
"lighting" : "combined",
"name" : "Label",
"position" : {
"scale" : 1.25,
"translation-in-points" : [
0,
0
]
},
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : false,
"translucency" : {
"enabled" : true,
"value" : 0.5
}
},
{
"blur-material" : 0.5,
"hidden" : false,
"layers" : [
{
"hidden" : false,
"image-name" : "pen-dark-color.png",
"name" : "pen-dark-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light-color.png",
"name" : "pen-light-color",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 1
},
{
"appearance" : "tinted",
"value" : 0
}
]
},
{
"hidden" : false,
"image-name" : "pen-light.png",
"name" : "pen-light",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 1
}
]
},
{
"glass" : true,
"hidden" : false,
"image-name" : "CodeEdit pen.png",
"name" : "CodeEdit pen",
"opacity-specializations" : [
{
"appearance" : "dark",
"value" : 0
},
{
"appearance" : "tinted",
"value" : 0
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Pen",
"position" : {
"scale" : 0.88,
"translation-in-points" : [
0,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.75
},
"specular" : false,
"translucency" : {
"enabled" : true,
"value" : 0.25
}
},
{
"blend-mode" : "normal",
"blur-material" : null,
"hidden" : false,
"layers" : [
{
"fill-specializations" : [
{
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,1.00000"
}
},
{
"appearance" : "dark",
"value" : "automatic"
},
{
"appearance" : "tinted",
"value" : {
"automatic-gradient" : "display-p3:1.00000,1.00000,1.00000,0.86284"
}
}
],
"glass" : true,
"hidden" : false,
"image-name" : "Slice 1 (9).svg",
"name" : "CodeLines",
"opacity-specializations" : [
{
"value" : 0.75
},
{
"appearance" : "dark",
"value" : 0.5
},
{
"appearance" : "tinted",
"value" : 0.33
}
],
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"name" : "Code Lines",
"position" : {
"scale" : 1.24,
"translation-in-points" : [
-2.4609375,
0
]
},
"shadow" : {
"kind" : "layer-color",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
File: .github/FUNDING.yml
These are supported funding model platforms
github: [CodeEditApp, austincondiff]
patreon: # Replace with a single Patreon username
open_collective: codeeditapp
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
File: .github/pull_request_template.md
Description
Related Issues
- #ISSUE_NUMBER
Checklist
- I read and understood the contributing guide as well as the code of conduct
- The issues this PR addresses are related to each other
- My changes generate no new warnings
- My code builds and runs on my machine
- My changes are all related to the related issue above
- I documented my code
Screenshots
File: .github/release-drafter.yml
categories:
- title: '✨ Enhancements'
labels:- 'enhancement'
- 'feature'
- title: '🐞 Bug Fixes'
labels:- 'bugfix'
- 'bug'
- title: '🧰 Maintenance'
label:- 'chore'
- 'documentation'
- 'dependencies'
template: |
Changes
$CHANGES
File: .github/stale.yml
Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
Label to use when marking an issue as stale
staleLabel: wontfix
Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
File: .github/ISSUE_TEMPLATE/bug_report.yml
name: 🐞 Bug report
title: 🐞
labels: bug
body:
type: textarea
attributes:
label: Description
placeholder: >-
A clear and concise description of what the bug is...
validations:
required: truetype: textarea
attributes:
label: To Reproduce
Steps to reliably reproduce the behavior.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: truetype: textarea
attributes:
label: Expected Behavior
placeholder: >-
A clear and concise description of what you expected to happen...
validations:
required: truetype: textarea
attributes:
label: Version Information
click on the version number on the welcome screen
value: |
CodeEdit: [e.g. 0.0.x-alpha.y]
macOS: [e.g. 13.2.1]
Xcode: [e.g. 14.2]type: textarea
attributes:
label: Additional Context
placeholder: >-
Any other context or considerations about the bug...type: textarea
attributes:
label: Screenshots
placeholder: >-
If applicable, please provide relevant screenshots or screen recordings...
File: .github/ISSUE_TEMPLATE/feature_request.yml
name: ✨ Feature request
title: ✨
labels: enhancement
body:
type: textarea
attributes:
label: Description
placeholder: >-
A clear and concise description of what you would like to happen...
validations:
required: truetype: textarea
attributes:
label: Alternatives Considered
placeholder: >-
Any alternative solutions or features you've considered...type: textarea
attributes:
label: Additional Context
placeholder: >-
Any other context or considerations about the feature request...type: textarea
attributes:
label: Screenshots
placeholder: >-
If applicable, please provide relevant screenshots or screen recordings...
File: .github/workflows/add-to-project.yml
name: Add new issues to project
on:
issues:
types:
- opened
jobs:
add-to-project:
name: Add new issues labeled with enhancement or bug to project
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
with:
# You can target a repository in a different organization
# to the issue
project-url: https://github.com/orgs/CodeEditApp/projects/3
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
labeled: enhancement, bug
label-operator: OR
File: .github/workflows/appcast.yml
name: Build and publish a new appcast file
on:
workflow_dispatch:
jobs:
jekyll:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎
uses: actions/checkout@v3
with:
# If you're using actions/checkout@v3 you must set persist-credentials to false in most cases for the deployment to work correctly.
persist-credentials: false
- name: Cache 📦
uses: actions/[email protected]
with:
path: AppCast/vendor/bundle
key: ${{ runner.os }}-gems-v1.0-${{ hashFiles('AppCast/Gemfile') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Ruby ♦️
uses: actions/[email protected]
with:
ruby-version: '2.7'
- name: Bundler 💎
working-directory: AppCast
env:
BUNDLE_PATH: vendor/bundle
run: |
gem install bundler
bundle install
- name: Build 🛠
working-directory: AppCast
env:
BUNDLE_PATH: vendor/bundle
JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: bundle exec jekyll build
- name: Publish 🚀
uses: JamesIves/github-pages-deploy-action@releases/v3
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: AppCast/_siteFile: .github/workflows/CI-bump-build-number.yml
name: CI - Update Build Number
on:
workflow_dispatch:
jobs:
upadate-build-number:
name: Update Build Number
runs-on: [self-hosted, macOS]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Bump Build Number
run: |
xcrun agvtool next-version -all
APP_BUILD=$(xcrun agvtool vers -terse)
echo "APP_BUILD=$APP_BUILD" >> $GITHUB_ENV
- name: Commit Changes
env:
APP_BUILD: ${{ env.APP_BUILD }}
run: |
git add .
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git commit -m "bump build number to $APP_BUILD"
- name: Push Changes
uses: ad-m/[email protected]
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: temp/bump-build-number
force: true
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
env:
APP_BUILD: ${{ env.APP_BUILD }}
with:
branch: temp/bump-build-number
delete-branch: true
base: main
title: Bump Build Number to ${{ env.APP_BUILD }}
body: Automatically bump build number of all targets to ${{ env.APP_BUILD }}File: .github/workflows/CI-pre-release.yml
name: CI - Pre Release
on:
workflow_dispatch:
jobs:
swiftlint:
name: SwiftLint
uses: ./.github/workflows/lint.yml
secrets: inherit
test:
name: Testing CodeEdit
needs: swiftlint
uses: ./.github/workflows/tests.yml
secrets: inherit
deploy:
if: github.repository_owner == 'CodeEditApp'
name: Deploying CodeEdit [Prerelease]
needs: [swiftlint, test]
uses: ./.github/workflows/pre-release.yml
secrets: inherit
ReleaseDrafter:
name: Release Drafter
needs: [swiftlint, test, deploy]
uses: ./.github/workflows/release-drafter.yml
File: .github/workflows/CI-pull-request.yml
name: CI - Pull Request
on:
pull_request:
branches:
- 'main'
workflow_dispatch:
jobs:
swiftlint:
name: SwiftLint
uses: ./.github/workflows/lint.yml
secrets: inherit
test:
name: Testing CodeEdit
needs: swiftlint
uses: ./.github/workflows/tests.yml
secrets: inherit
File: .github/workflows/CI-release-notes.yml
name: Deploy Website on Release Note Changes
on:
workflow_dispatch:
release:
types: [created, edited, deleted]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Send deploy request to Vercel
run: curl -X POST ${{ secrets.VERCEL_DEPLOY_URL }}
File: .github/workflows/issue.yml
name: Labeling new issue
on:
issues:
types: ['opened']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: Renato66/auto-label@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
ignore-comments: true
labels-synonyms: '{"bug":["error","need fix","not working"],"enhancement":["upgrade"],"question":["help"]}'
labels-not-allowed: '["good first issue", "priority"]'
default-labels: '["triage needed"]'
File: .github/workflows/lint.yml
name: SwiftLint
on:
workflow_dispatch:
workflow_call:
jobs:
SwiftLint:
runs-on: [self-hosted, macOS] # ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: GitHub Action for SwiftLint
run: swiftlint --reporter github-actions-logging --strict
File: .github/workflows/pre-release.yml
name: Build and Publish Pre-release
on:
workflow_dispatch:
workflow_call:
jobs:
pre-release:
name: Build and Publish Pre-release
runs-on: [self-hosted, macOS]
steps:
- name: Checkout repository
uses: actions/checkout@v3
############################
# Install Certificate
############################
- name: Install codesign certificate
env:
# DEV_CERT_B64: Base64-encoded developer certificate as .p12
# DEV_CERT_PWD: Developer certificate .p12 password
# PROVISION_PROFILE_B64: Base64-encoded provisioning profile as .provisionprofile
# KEYCHAIN_TIMEOUT: Lock keychain after timeout interval
# https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development
DEV_CERT_B64: ${{ secrets.DEV_CERT_B64 }}
DEV_CERT_PWD: ${{ secrets.DEV_CERT_PWD }}
MAC_CERT_B64: ${{ secrets.MAC_CERT_B64 }}
MAC_CERT_PWD: ${{ secrets.MAC_CERT_PWD }}
# PROVISION_PROFILE_B64: ${{ secrets.PROVISION_PROFILE_B64 }}
KEYCHAIN_TIMEOUT: 21600
run: |
DEV_CERT_P12="$RUNNER_TEMP/dev_cert.p12"
MAC_CERT_P12="$RUNNER_TEMP/mac_cert.p12"
KEYCHAIN_DB="$RUNNER_TEMP/keychain.keychain-db"
KEYCHAIN_PWD=$(openssl rand -base64 24)
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN_DB"
security set-keychain-settings -lut "$KEYCHAIN_TIMEOUT" "$KEYCHAIN_DB"
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN_DB"
echo -n "$DEV_CERT_B64" | base64 --decode -o "$DEV_CERT_P12"
security import "$DEV_CERT_P12" -P "$DEV_CERT_PWD" -A -t cert -f pkcs12 -k "$KEYCHAIN_DB"
echo -n "$MAC_CERT_B64" | base64 --decode -o "$MAC_CERT_P12"
security import "$MAC_CERT_P12" -P "$MAC_CERT_PWD" -A -t cert -f pkcs12 -k "$KEYCHAIN_DB"
security list-keychain -d user -s "$KEYCHAIN_DB"
############################
# Build
############################
- name: Build CodeEdit
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: xcodebuild -scheme CodeEdit -configuration Pre -derivedDataPath "$RUNNER_TEMP/DerivedData" -archivePath "$RUNNER_TEMP/CodeEdit.xcarchive" -skipPackagePluginValidation DEVELOPMENT_TEAM=$APPLE_TEAM_ID archive | xcpretty
############################
# Sign
############################
- name: Sign CodeEdit
env:
CODESIGN_SIGN: ${{ secrets.CODESIGN_SIGN }}
run: |
REV=$(git rev-parse --short HEAD)
echo "REV=$REV" >> $GITHUB_ENV
codesign --sign "$CODESIGN_SIGN" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --timestamp "$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app/Contents/Frameworks/CodeEditKit.framework"
codesign --sign "$CODESIGN_SIGN" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --timestamp "$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app/Contents/Frameworks/CodeLanguages_Container.framework"
codesign --sign "$CODESIGN_SIGN" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --deep --timestamp "$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app/Contents/Frameworks/Sparkle.framework"
codesign --sign "$CODESIGN_SIGN" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --timestamp --entitlements "$RUNNER_TEMP/../CodeEdit/CodeEdit/OpenWithCodeEdit/OpenWithCodeEdit.entitlements" "$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app/Contents/PlugIns/OpenWithCodeEdit.appex"
codesign --sign "$CODESIGN_SIGN" -vvv --verbose --strict --options=runtime --prefix app.codeedit.CodeEdit. --force --timestamp "$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/CodeEdit.app"
############################
# DMG & Notarize
############################
- name: Create DMG & Notarize
env:
CODESIGN_SIGN: ${{ secrets.CODESIGN_SIGN }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PWD: ${{ secrets.APPLE_ID_PWD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
RUNNER_PW: ${{ secrets.RUNNER_PW }}
run: |
REV=$(git rev-parse --short HEAD)
echo "REV=$REV" >> $GITHUB_ENV
security unlock-keychain -p "$RUNNER_PW"
xcrun notarytool store-credentials CodeEdit --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_ID_PWD"
cp "$RUNNER_TEMP/../CodeEdit/CodeEdit/Resources/dmgBackground.png" "$RUNNER_TEMP/.background.png"
epochdate=$(($(date +'%s * 1000 + %-N / 1000000')))
tcc_service_appleevents="replace into access (service,client,client_type,auth_value,auth_reason,auth_version,indirect_object_identifier_type,indirect_object_identifier,flags,last_modified) values (\"kTCCServiceAppleEvents\",\"/usr/sbin/sshd\",1,2,4,1,0,\"com.apple.finder\",0,$epochdate);"
echo $RUNNER_PW | sudo -S sqlite3 "/Users/administrator/Library/Application Support/com.apple.TCC/TCC.db" "$tcc_service_appleevents"
create-dmg \
--volname "CodeEdit" \
--window-pos 200 120 \
--window-size 699 518 \
--background "$RUNNER_TEMP/.background.png" \
--icon-size 128 \
--icon "CodeEdit.app" 170 210 \
--hide-extension "CodeEdit.app" \
--app-drop-link 530 210 \
--codesign "$CODESIGN_SIGN" \
--notarize "CodeEdit" \
"$RUNNER_TEMP/CodeEdit.dmg" \
"$RUNNER_TEMP/CodeEdit.xcarchive/Products/Applications/"
echo $RUNNER_PW | sudo -S security lock-keychain
############################
# Get Version and Build number
############################
- name: Get Version and Build number
run: |
APP_VERSION=$(xcrun agvtool mvers -terse1)
APP_BUILD=$(xcrun agvtool vers -terse)
echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV
echo "APP_BUILD=$APP_BUILD" >> $GITHUB_ENV
############################
# Upload dSYMs Artifact
############################
- name: Upload dSYMs Artifact
uses: actions/upload-artifact@v4
with:
name: "CodeEdit-${{ env.APP_BUILD }}-dSYMs"
path: "${{ RUNNER.TEMP }}/CodeEdit.xcarchive/dSYMs"
if-no-files-found: error
# overwrite files for the same build number
overwrite: true
# these can be big, use maximum compression
compression-level: 9
############################
# Sparkle Appcast
############################
- name: Update Sparkle appcast
env:
# SPARKLE_KEY: Sparkle EdDSA key exported from `generate_keys -x` as plain text
# SPARKLE_CHANNEL: Seperate dev builds from default channel, to be specified in [SPUUpdaterDelegate allowedChannelsForUpdater:]
# SPARKLE_DL_PREFIX: Prefix for the URL from where updates will be downloaded
# SPARKLE_LINK: CodeEdit Website
# https://github.com/CodeEditApp/CodeEdit/releases/download/0.0.1-alpha.11/CodeEdit-9113dc5.dmg
# RELEASE_NOTES_PREFIX: The URL to prefix before an update link:
# https://codeedit.app/whats-new/raw/{v0.1.0} -- data in {} is inserted by sparkle
# RELEASE_NOTES_URL: The URL of the entire release notes page: https://codeedit.app/whats-new
SPARKLE_KEY: ${{ secrets.SPARKLE_KEY }}
SPARKLE_CHANNEL: dev
SPARKLE_DL_PREFIX: "https://github.com/CodeEditApp/CodeEdit/releases/download"
SPARKLE_LINK: "https://github.com/CodeEditApp/CodeEdit"
APP_VERSION: ${{ env.APP_VERSION }}
APP_BUILD: ${{ env.APP_BUILD }}
RELEASE_NOTES_URL: "https://codeedit.app/whats-new/"
RELEASE_NOTES_PREFIX: "https://codeedit.app/sparkle/"
run: |
SPARKLE_BIN="$RUNNER_TEMP/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin"
SPARKLE_ARCHIVE="$RUNNER_TEMP/Sparkle_Archive"
echo -n "$SPARKLE_KEY" | tee "$RUNNER_TEMP/sparkle_key"
mkdir "$SPARKLE_ARCHIVE"
cp "$RUNNER_TEMP/CodeEdit.dmg" "$SPARKLE_ARCHIVE"
SPARKLE_SIG=$("$SPARKLE_BIN/sign_update" --ed-key-file "$RUNNER_TEMP/sparkle_key" "$SPARKLE_ARCHIVE/CodeEdit.dmg" | cut -d\" -f2)
echo "<!DOCTYPE>" > "$SPARKLE_ARCHIVE/CodeEdit.html" # Need a blank html doc with the DOCTYPE tag to trick sparkle into loading our remote release notes.
"$SPARKLE_BIN/generate_appcast" --ed-key-file "$RUNNER_TEMP/sparkle_key" --download-url-prefix "${{ env.SPARKLE_DL_PREFIX }}/v${{ env.APP_VERSION }}/" --link "$SPARKLE_LINK" --channel "$SPARKLE_CHANNEL" --maximum-deltas 0 "$SPARKLE_ARCHIVE" --release-notes-url-prefix "${{ env.RELEASE_NOTES_PREFIX }}v${{ env.APP_VERSION }}/" --full-release-notes-url "$RELEASE_NOTES_URL"
############################
# Publish Pre Release
############################
- name: Publish Pre-release
uses: marvinpinto/action-automatic-releases@latest
env:
APP_VERSION: ${{ env.APP_VERSION }}
APP_BUILD: ${{ env.APP_BUILD }}
with:
title: "v${{ env.APP_VERSION }}"
files: |
${{ RUNNER.TEMP }}/Sparkle_Archive/CodeEdit.dmg
${{ RUNNER.TEMP }}/Sparkle_Archive/appcast.xml
automatic_release_tag: "v${{ env.APP_VERSION }}"
prerelease: false
repo_token: "${{ secrets.GITHUB_TOKEN }}"
draft: true
############################
# Cleanup Secrets
############################
- name: Clean up keychain and provisioning profile
if: ${{ always() }}
run: |
security delete-keychain "$RUNNER_TEMP/keychain.keychain-db"
rm -rf "~/Library/MobileDevice/Provisioning Profiles"File: .github/workflows/release-drafter.yml
name: Release Drafter
on:
Allow running it manually in case we forget to label a PR before merging
workflow_dispatch:
workflow_call:
jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
- id: release
uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
File: .github/workflows/tests.yml
name: Tests
on:
workflow_dispatch:
workflow_call:
jobs:
test:
name: Testing CodeEdit
runs-on: [self-hosted, macOS]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Check Version Number exists
run: exec ./.github/scripts/test_version_number.sh
- name: Testing App
run: exec ./.github/scripts/test_app.sh arm