Public betaHelp shape it ›

A real editor for everything
Xcode isn’t for.

A native macOS editor with language intelligence, Git, terminal, and debugger. Built in Swift and AppKit.

macOS 14 or later

A recreation of the Codeworks editor: a project is opened from the file finder, a guard clause is typed into a source file, the language service flags an undefined name, a quick fix declares it, the developer switches to the tests that cover that function, and the suite runs green.
CodeworksmainCodeworksTestsTesting CodeworksTest Succeeded1
//
// OpenQuicklyViewModel+BoundedSearch.swift
// Codeworks
//
import Foundation
extension OpenQuicklyViewModel {
static let maximumQueryLength = 128
/// Keeps only the best finite result set, avoiding one child task and one retained match per file.
///
/// Matching runs against the **workspace-relative path**, not the bare filename: `feat/ed/view`
/// is how a VS Code or Xcode user types, and against filenames alone it returned nothing. The
/// filename is matched a second time (only for candidates the path already matched) so that a
/// hit *in the name* still outranks one that lives entirely in directory components, and so the
/// row's highlight stays in filename coordinates.
static func boundedFuzzySearch(
_ files: [URL],
query: String,
rootURL: URL? = nil,
maximumResults: Int = maximumPublishedResults
) throws -> [SearchResult] {
guard maximumResults > 0 else { return [] }
let rootPrefix = rootURL.map { $0.path.hasSuffix("/") ? $0.path : $0.path + "/" }
var best = BoundedSearchResultHeap(capacity: maximumResults)
for (ordinal, file) in files.enumerated() {
try Task.checkCancellation()
let relativePath = Self.relativePath(of: file, rootPrefix: rootPrefix)
let pathMatch = FuzzyPath(searchableString: relativePath).fuzzyMatch(query: query)
guard pathMatch.weight > 0 else { continue }
let nameMatch = file.fuzzyMatch(query: query)
best.insert(
RankedSearchResult(
result: SearchResult(fileURL: file, matchedCharacters: nameMatch.matchedParts),
weight: pathMatch.weight + nameMatch.weight,
length: relativePath.count,
ordinal: ordinal
)
)
}
return best.sortedResults()
}
static func relativePath(of file: URL, rootPrefix: String?) -> String {
guard let rootPrefix, file.path.hasPrefix(rootPrefix) else { return file.path }
return String(file.path.dropFirst(rootPrefix.count))
}
}
/// Fuzzy-matches an arbitrary string. `URL`'s own conformance searches the last path component
/// only, and is shared with other call sites — so the path candidate gets its own wrapper instead
/// of a global change to `URL.searchableString`.
private struct FuzzyPath: FuzzySearchable {
let searchableString: String
}
private struct RankedSearchResult {
let result: OpenQuicklyViewModel.SearchResult
let weight: Int
/// Length of the matched path — the tiebreak that keeps `View.swift` above
/// `Features/Editor/Views/EditorView.swift` for the query `view` (both score the same run).
let length: Int
let ordinal: Int
static func isWorse(_ lhs: Self, than rhs: Self) -> Bool {
if lhs.weight != rhs.weight { return lhs.weight < rhs.weight }
if lhs.length != rhs.length { return lhs.length > rhs.length }
return lhs.ordinal > rhs.ordinal
}
}
private struct BoundedSearchResultHeap {
let capacity: Int
private var heap: [RankedSearchResult] = []
init(capacity: Int) {
self.capacity = capacity
}
mutating func insert(_ candidate: RankedSearchResult) {
if heap.count < capacity {
heap.append(candidate)
siftUp(from: heap.count - 1)
} else if let worst = heap.first, RankedSearchResult.isWorse(worst, than: candidate) {
heap[0] = candidate
siftDown(from: 0)
}
}
func sortedResults() -> [OpenQuicklyViewModel.SearchResult] {
heap.sorted { lhs, rhs in
RankedSearchResult.isWorse(rhs, than: lhs)
}.map(\.result)
}
private mutating func siftUp(from index: Int) {
var child = index
while child > 0 {
let parent = (child - 1) / 2
guard RankedSearchResult.isWorse(heap[child], than: heap[parent]) else { return }
heap.swapAt(child, parent)
child = parent
}
}
private mutating func siftDown(from index: Int) {
var parent = index
while true {
let left = parent * 2 + 1
guard left < heap.count else { return }
let right = left + 1
var worseChild = left
if right < heap.count, RankedSearchResult.isWorse(heap[right], than: heap[left]) {
worseChild = right
}
guard RankedSearchResult.isWorse(heap[worseChild], than: heap[parent]) else { return }
heap.swapAt(parent, worseChild)
parent = worseChild
}
}
}
//
// OpenQuicklyInventoryBudgetTests.swift
// CodeworksTests
//
import Foundation
import Testing
@testable import Codeworks
@Suite
struct OpenQuicklyInventoryBudgetTests {
@Test
func enumerationStopsAtVisitedEntryLimit() throws {
let root = FileManager.default.temporaryDirectory
.appending(path: "open-quickly-budget-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: root) }
for index in 0..<3 {
try Data().write(to: root.appending(path: "\(index).swift"))
}
#expect(throws: OpenQuicklyViewModel.InventoryError.self) {
try OpenQuicklyViewModel.filteredWorkspaceFiles(
at: root,
ignorePatterns: [],
maximumEntries: 2
)
}
}
@Test
func ignoredDirectoryIsPrunedBeforeItsContentsConsumeBudget() throws {
let root = FileManager.default.temporaryDirectory
.appending(path: "open-quickly-prune-\(UUID().uuidString)")
let ignored = root.appending(path: "node_modules")
try FileManager.default.createDirectory(at: ignored, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: root) }
for index in 0..<20 {
try Data().write(to: ignored.appending(path: "\(index).js"))
}
try Data().write(to: root.appending(path: "main.swift"))
let files = try OpenQuicklyViewModel.filteredWorkspaceFiles(
at: root,
ignorePatterns: [],
maximumEntries: 3
)
#expect(files.map(\.lastPathComponent) == ["main.swift"])
}
@MainActor
@Test
func staleSuccessCannotOverwriteNewerResults() {
let model = OpenQuicklyViewModel(fileURL: URL(fileURLWithPath: "/workspace"))
let staleGeneration = model.advanceSearchGeneration()
let currentGeneration = model.advanceSearchGeneration()
let stale = OpenQuicklyViewModel.SearchResult(
fileURL: URL(fileURLWithPath: "/workspace/stale.swift"),
matchedCharacters: []
)
let current = OpenQuicklyViewModel.SearchResult(
fileURL: URL(fileURLWithPath: "/workspace/current.swift"),
matchedCharacters: []
)
model.publishSuccess([current], generation: currentGeneration)
model.publishSuccess([stale], generation: staleGeneration)
#expect(model.searchResults == [current])
#expect(model.errorMessage == nil)
}
@MainActor
@Test
func staleFailureCannotEraseNewerResultsOrSetAnError() {
let model = OpenQuicklyViewModel(fileURL: URL(fileURLWithPath: "/workspace"))
let staleGeneration = model.advanceSearchGeneration()
let currentGeneration = model.advanceSearchGeneration()
let current = OpenQuicklyViewModel.SearchResult(
fileURL: URL(fileURLWithPath: "/workspace/current.swift"),
matchedCharacters: []
)
model.publishSuccess([current], generation: currentGeneration)
model.publishFailure("stale failure", generation: staleGeneration)
#expect(model.searchResults == [current])
#expect(model.errorMessage == nil)
}
@MainActor
@Test
func whitespaceOnlyQueryDoesNotStartWorkspaceEnumeration() {
let model = OpenQuicklyViewModel(fileURL: URL(fileURLWithPath: "/workspace"))
model.query = " \n\t "
model.fetchResults()
#expect(OpenQuicklyViewModel.normalizedQuery(model.query) == nil)
#expect(model.runningTask == nil)
#expect(model.searchResults.isEmpty)
#expect(!model.isLoading)
}
@Test
func fuzzySearchRetainsAtMostConfiguredResultCount() throws {
let files = (0..<20).map { index in
URL(fileURLWithPath: "/workspace/file-\(index).swift")
}
let results = try OpenQuicklyViewModel.boundedFuzzySearch(
files,
query: "file",
maximumResults: 3
)
#expect(results.count == 3)
#expect(Set(results.map(\.fileURL)).isSubset(of: Set(files)))
}
@Test
func queryPastMaximumLengthReturnsNoResults() throws {
let limit = OpenQuicklyViewModel.maximumQueryLength
let overlong = String(repeating: "a", count: limit + 1)
let files = [URL(fileURLWithPath: "/workspace/\(overlong).swift")]
let results = try OpenQuicklyViewModel.boundedFuzzySearch(
files,
query: overlong
)
#expect(results.isEmpty)
}
}

Every project on your Mac, in one native window. Web apps, scripts, services, infrastructure: the languages Xcode was never for, with the responsiveness you expect from a Mac app.

Built for the way you work.

Focused defaults, with language tools, Git, a terminal, and debugging when you need them.

Native editor

A Swift and AppKit editor designed for macOS windows, shortcuts, text input, and system behavior. No web view, no runtime between you and the text.

Language intelligence

Completion, diagnostics, hover, rename, references, formatting, and code actions through language servers.

Git built in

Review changes, inspect diffs and blame, browse history, switch branches, and commit without leaving the workspace.

Terminal and tasks

Run shells and project commands beside your editor, with task output kept inside the workspace.

Native debugging

Launch LLDB, set breakpoints, step through code, inspect frames and variables, and read console output.

Yours to configure

Choose themes, remap keybindings, save snippets, and tune editing behavior without an account.

Beta

Meet X Agent.

An AI agent built into your editor. It reads your project and works across files, and every edit it proposes stops at a diff you have to approve before anything reaches disk.

A session with X Agent: asked to add a retries option to the API client, it searches the project, prepares edits to api/client.ts and api/session.ts, and presents both as a diff with Reject and Accept controls. Nothing is written to disk until the change is accepted, and an accepted change reverts in a single undo.

A clear privacy boundary.

Your code is yours. Codeworks does not upload it to Loopware.

Loopware boundary

Codeworks does not upload your files or project contents to Loopware.

Connections you initiate

Git providers and language tools may send project data under their own settings and privacy policies.

Diagnostics by choice

Crash reports stay local unless you explicitly choose to email one to support.

Read the privacy policy ›

Built on a remarkable foundation.

Codeworks began with CodeEdit, an ambitious open-source attempt to build the native macOS editor we always wanted. When development slowed, we did not want that idea to end there. So six of us picked it up and started imagining what could come next.

Read our story ›

A new build every week.

Six of us work on Codeworks full time, and a signed build ships every week. That is the whole loop: you report it, we fix it, it reaches your Mac days later.

Not a side project

Six of us design, build, test, and support Codeworks every day. It is the only thing we work on.

A build every week

A signed update lands weekly on the channel you follow, and the app installs it in place.

From report to release

A bug filed this week is usually fixed in the next build, not held for a release months away.

Open the feedback board ›

Ready to try Codeworks?

Download the signed Mac app. It is free while Codeworks is in public beta.

macOS 14 or later. Codeworks is a public beta: expect rough edges, and know that the feedback board decides what we fix and build next.