Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ objc2-core-graphics = { version = "0.3", default-features = false, features = [
"CGPDFPage",
] }
parking_lot = "0.12"
# The poller alacritty_terminal's PTY registers with, named to wrap that PTY
# (a web app's script's output read as lines); already built for it.
polling = "3"
# A window's AppKit view, for its window number (screenshots).
raw-window-handle = "0.6"
regex = "1"
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Xcode projects and Swift packages without opening Xcode. It uses the
toolchain Xcode installs: `xcodebuild`, `simctl`, `sourcekit-lsp` and
`lldb-dap`. The web app an Apple app ships with (a Vite app in `web/`, the
pages of a web view) is edited in the same window, with the web's language
servers and Emmet.
servers and Emmet, and run there: its `package.json` scripts are in the
scheme menu beside the app's schemes, and Run starts its dev server next
to the running app.

```
cargo build --release
Expand Down
18 changes: 15 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ src/
Profile's template chooser (profile.rs); Archive, the Organizer tab and Distribute App's
sheet (archive.rs, organizer.rs, distribute_sheet.rs); the asset catalog editor as a tab
(asset_catalog.rs: outline, wells, inspector, edits and undo; asset_catalogs.rs: opening it);
SwiftUI previews' gutter marks and Show in Xcode Canvas (previews.rs)
SwiftUI previews' gutter marks and Show in Xcode Canvas (previews.rs); web apps' scripts
run in the debug area's terminals beside the scheme's run, their URLs and issues
(web_runs.rs)
bsp/ the Build Server Protocol server sourcekit-lsp starts for an Xcode project (`zcode bsp`,
relayed to the running zcode's socket; its connection file found through XDG_DATA_HOME):
the build graph (graph.rs: targets, files, arguments from Xcode's index settings, the
Expand All @@ -40,7 +42,9 @@ src/
compilation-database extraction; profile/: Product ▸ Profile (xctrace's templates and
recording, opening Instruments, a Time Profiler export's heaviest functions); archive/:
Product ▸ Archive and Distribute App (archives in Xcode's folder read from their
Info.plist, ExportOptions.plist and xcodebuild -exportArchive, notarytool and stapler)
Info.plist, ExportOptions.plist and xcodebuild -exportArchive, notarytool and stapler);
web.rs: web apps' package.json scripts as run targets, the package manager a lockfile
implies, a script's output read for its dev server's URL and its errors
packages/ Swift package dependencies: rules and versions, Package.resolved, checkouts,
Package.swift edits, git queries, resolve/update/reset
testing/ tests found in source, test plans, test selections per runner, results → tests
Expand Down Expand Up @@ -75,7 +79,8 @@ src/
codesign, the swap into the bundle's place, Restart Now
debugger/ DAP client, debugger model, breakpoints and their options, data tips (lldb-dap)
terminal/ the integrated terminal: a PTY and its grid (alacritty_terminal), keystrokes and
mouse reports as bytes, links, theme colors, the view and how its grid is painted
mouse reports as bytes, links, theme colors, the view and how its grid is painted;
a program's output also read as plain lines (lines.rs) for a web app's script
project.rs project detection and gitignore-aware scanning
xcodeproj.rs project.pbxproj parser and in-place editor (build settings)
xcodeproj/ structural edits: files, groups, build files, target membership, packages,
Expand Down Expand Up @@ -285,6 +290,13 @@ method names and search order, none of it copied). It asks Xcode's own
`xcodebuild -showBuildSettingsForIndex` and `-showBuildSettings`.
xcode-build-server (MIT) does the same job from build logs; none of its
code is used. No new dependencies.
Running a web app's scripts follows the package managers' documented
lockfiles and `packageManager` field (Corepack's); the scheme menu's place
for them, Run and Stop are Xcode's, and the dev servers' and compilers'
output formats were read from their documentation and output. A script's
output is read from the PTY alacritty_terminal already reads, through the
`polling` crate's types its PTY registers with (MIT OR Apache-2.0, already
built for it). No new dependencies.
The scale benchmarks (`scripts/bench.py`) run zcode on firefox-ios
(Mozilla Public License 2.0), fetched at a pinned commit into a cache
outside the repository and only read; nothing of it is copied into zcode.
Expand Down
1 change: 1 addition & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
| **Web apps** (Vite: HTML, CSS, JavaScript, TypeScript, Vue, Svelte, Astro) | The same language features for a web app inside the project, from the servers VS Code and Zed use: vtsls (JavaScript, TypeScript, JSX and TSX: completions with auto-imports, diagnostics, Quick Help, Jump to Definition, references, rename, code actions, inlay hints, semantic highlighting), the HTML, CSS (SCSS, Less) and JSON servers (JSON Schema Store's schemas for `package.json`, `tsconfig.json` and the other config files), Vue (Volar, with its TypeScript plugin in vtsls: zcode relays its `tsserver/request`s), Svelte and Astro, and, when the app uses them, ESLint (its fixes as code actions), Biome and Tailwind CSS (class completions, hover and color swatches). Each runs for the web app a file is in: the outermost folder with a `package.json`, `tsconfig.json` or `jsconfig.json` (an Xcode project's `web/` gets servers of its own next to sourcekit-lsp), started by the first file it serves. A file has every server for it: completions, code actions, colors and diagnostics are all of theirs; the first that answers gives Quick Help and definitions. The status bar shows the focused file's first server (installing, starting, indexing, failed) and how many more it has. Files in `node_modules` open read-only. | Editor ▸ Restart Language Servers |
| | Where the servers come from: `lsp.<id>.binary` in your settings, the web app's own `node_modules/.bin`, your login shell's `PATH`, or zcode installs them with npm into `~/Library/Application Support/zcode/language-servers` the first time one is needed (Node.js required; `auto_install_language_servers: false` turns that off). `lsp.<id>` also takes `enabled`, `initialization_options` and `settings` (merged over zcode's), as in Zed. Format File and `format_on_save` use the web app's Prettier when it has one (`formatter`: `auto`, `prettier` or `language_server`), Biome's formatter for a Biome app, the language server otherwise. | ⌃⇧I |
| | Emmet, built in (no Node): an abbreviation before the cursor is offered as a completion in HTML, the markup of Vue, Svelte and Astro files, JSX and TSX (`className`, `<img />`), and CSS, SCSS and Less (and a component's `<style>`): `ul.list>li*3`, `.card>h2{Title}+p`, `!`, `a:link`, `input:email`, `lorem10`, `m10-20`, `d:f`, `c#fc0`… Expanded, its first tab stop is selected and Tab visits the rest. Editor ▸ Emmet ▸ Expand Abbreviation expands without the menu; Wrap with Abbreviation… puts the selection (or the line) inside one, previewed as you type it (`ul>li*` makes an item of each line). Tags close as their `>` is typed in HTML, Vue, Svelte, Astro, JSX and TSX; ⌘/ comments markup as `<!-- -->` and CSS as `/* */` (a component's script and style blocks in their own language). | ⌃⌥E |
| | Running the web app: the scheme menu (and Choose Scheme…) has a section per web app, named for its folder (`web`), listing its `package.json` scripts; choosing one makes Run run it, and a plain folder with a `package.json` runs this way too. A script runs with the package manager its lockfile (or `packageManager`) implies (npm, pnpm, yarn or bun; npm without one), in its folder, with your login shell's `PATH` and its `node_modules/.bin`, in a terminal of the debug area's Terminal tab named for it (`web: dev`), so colors and a dev server's keys work; running it again replaces that run. It runs beside the scheme's build and app: a dev server keeps running while the app runs, and the other way round; Stop ends the chosen target's run (a script's, else the build and app, and with neither going, the scripts). The first local URL it prints (Vite's, Next's, webpack-dev-server's, Astro's, any `http://localhost:NNNN`) opens in your browser once per window, not again on each rerun (`web.open_dev_server_url`) and stays in the activity view, a click away; errors it prints (TypeScript in both formats, esbuild, Vite's, ESLint's, `file:line:col: error:`) are build issues in the Issue navigator and the editor until it runs again (or a watcher starts over), with a failed run's exit code. Edit Scheme… shows the script in `package.json`. The choice comes back with the session. | ⌘R, ⌘., ⌃0 |
| | Find ▸ Find Call Hierarchy: the function at the cursor (in whichever pane is focused) as a tree in the Find navigator, with its callers (as in Xcode) or the calls it makes, each level expanded on demand; a caller opens at its call, the rest at their declarations, and a row's menu starts a new hierarchy from it. Find Ancestor Types, Find Descendent Types and Find Conforming Types show a type's supertypes or subtypes the same way. Editor ▸ Show Callers… lists the callers in a menu at the cursor. Features the language server doesn't offer are left out, and their commands say so. | ⌃⇧⌘H |
| **Xcode project indexing** | sourcekit-lsp learns how to compile an `.xcodeproj`'s files from zcode's own Build Server Protocol server, the job xcode-build-server does: each target's files come from the project (build phases, synchronized folders and their exceptions), their arguments from Xcode's own for indexing (`xcodebuild -showBuildSettingsForIndex`), the last index build's log, or the target's build settings. A file added to a target, a membership change or a build setting edited (in zcode, Xcode or an `.xcconfig` saved in zcode) reaches sourcekit-lsp within seconds, without a build, and a target the build never compiled still gets its arguments. zcode writes nothing into the project: sourcekit-lsp finds `zcode bsp` through a connection file in zcode's cache, and a project's own build server (`buildServer.json`, `.bsp/`) wins. A low-priority background "index build" writes the index (and a compilation database, which `"sourcekit_build_settings": "compilation_database"` hands sourcekit-lsp instead); saving source files refreshes it, so rename and references stay current. | — |
| **Navigation** | Open Quickly (fuzzy file and symbol search, `file:line`), Quick Actions command palette, back/forward history, jump to line, counterpart (.h ↔ .m), reveal in navigator and tab switching. | ⇧⌘O, ⇧⌘A, ⌃⌘← ⌃⌘→, ⌘L, ⌃⌘↑, ⇧⌘J, ⇧⌘[ ⇧⌘] |
Expand Down
29 changes: 25 additions & 4 deletions docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,33 @@
semantic highlighting come from a file's first server that has them (a
Vue file's are Vue's server's). Vue's `{{ }}` interpolations aren't
highlighted as JavaScript (directives' values are). There's no linked
editing of a tag's pair, no ESLint fix-all on save, no running or
debugging the web app (the terminal runs `npm run dev`) and no
browser preview. Emmet works in markup, JSX, TSX and stylesheets (not in
editing of a tag's pair, no ESLint fix-all on save, no debugging
the web app and no browser preview (see running it below). Emmet works in markup, JSX, TSX and stylesheets (not in
`.js` or `.ts` files), without CSS at-rules, vendor prefixes, BEM and
other filters, or Emmet's other actions (Balance, Go to Edit Point,
Update Tag); a snippet's repeated tab stops aren't linked (typing in
one doesn't change the others).
* Running a web app's scripts: scripts come from `package.json` files the
project's scan finds (gitignored folders and `node_modules` aren't
searched), read again when one changes; a `package.json` added later
shows once the project is scanned again (a file created in zcode, a
git refresh). Workspace (monorepo) scripts run in their own folder
with the root's lockfile's manager, not through `--filter`/`-w`; a
package manager's version (Corepack) isn't checked. Build, Test,
Profile and Archive keep using the project's scheme while a script is
chosen, and a folder without a scheme has none of them. Stop ends the
chosen target's run; with several things running there's no menu to
pick one (choose it, then ⌘., or close its terminal tab). Only the
first local URL is taken (a server printing another later, or only a
LAN address, isn't followed), and it's opened in the default browser.
Errors are recognized in the formats listed in the features (others,
e.g. Jest's or Svelte's, stay in the terminal); an error whose
location comes on a later line is matched within 40 lines; a
watcher's issues clear when it says it's starting over (tsc's
`File change detected`, Vite's `hmr update` and `page reload`), so a
dev server that fixes an error silently keeps it until the next run.
A run's terminal and output aren't kept across a relaunch (the choice
is). Runs are tested against a fake package manager and Vite.
* Multibuffers: excerpts are highlighted in the language most of their
files are in, and have no completion, Quick Help, Jump to Definition or
inlay hints (⌥↩ opens the file, where they work); bookmarks, test
Expand All @@ -187,7 +207,8 @@
settings of its own (shell, font, colors, scrollback size) or blinking
cursor. A restored terminal is a new shell in the old folder: what ran
in it and its scrollback are gone. Terminals can't be moved between the
debug area and editor tabs. URLs open in the browser through macOS; the
debug area and editor tabs (a web app's script always runs in the debug
area's). URLs open in the browser through macOS; the
e2e checks don't open one (their detection is unit-tested), and Open
Terminal at Location is driven through the same command its menu item
calls rather than a click on the context menu.
Expand Down
8 changes: 4 additions & 4 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ always works on its own window's project.

| Tool | What it does |
|---|---|
| `XcodeListSchemes`, `XcodeSwitchScheme` | The schemes, configurations and test plans, and which are chosen; choose a scheme as the toolbar does |
| `XcodeListSchemes`, `XcodeSwitchScheme` | The schemes and the web apps' `package.json` scripts (`web: dev`, with each run target's kind), configurations and test plans, and which are chosen; choose one as the toolbar does |
| `XcodeListRunDestinations`, `XcodeSwitchRunDestination` | My Mac, simulators (booted or not) and devices for the scheme; choose one |
| `BuildProject` | Product ▸ Build: waits, then success, the errors and warnings (file, line, column) and the log's file |
| `RunProject` | Product ▸ Run (under the debugger when breakpoints are set): returns once it's running, with its first console output |
| `StopProject` | Product ▸ Stop: the build, the app and the debugger |
| `RunProject` | Product ▸ Run (under the debugger when breakpoints are set): returns once it's running, with its first console output. A script runs in the debug area's terminal beside a running app; it returns its first output, its dev server's URL and the errors it printed |
| `StopProject` | Product ▸ Stop: the chosen run target's run (a script's, or the build, the app and the debugger), or `scheme`'s |
| `RunAllTests`, `RunSomeTests` | Product ▸ Test, or some tests by the Test navigator's names: every test's result and duration, the failures at their lines |
| `GetTestList` | The tests found in source, with where they are and their latest results |
| `XcodeListNavigatorIssues` | The Issue navigator: live sourcekit-lsp diagnostics and the last build's issues, for a file or all |
| `GetBuildLog` | The build log, condensed or raw, filtered; the last builds' reports and log files |
| `GetConsoleOutput` | The app's and the debugger's console |
| `GetConsoleOutput` | The app's and the debugger's console and the scripts running; with `scheme`, a script's output |
| `XcodeGetCurrentFile` | The focused editor's file, cursor, selection and selected text, unsaved changes, and its text if asked |
| `OpenFile` | Opens a file at a line and column, so you see it |
| `EvaluateInDebugger` | An LLDB command (`po`, `p`, `v`, `bt`, `expr`) in the paused frame, shown in the console |
Expand Down
1 change: 1 addition & 0 deletions docs/parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ which was extracted from Xcode 27's own command definitions.
| sourcekit-lsp: completion, diagnostics, hover, ⌃⌘J, references, ⌃6 | ✅ | `.xcodeproj` files through zcode's own Build Server Protocol server (`zcode bsp`; no xcode-build-server needed, nothing written into the project): targets and files from the project, arguments from Xcode's index settings, the index build's log or the target's build settings, so new files, membership and build-setting changes reach sourcekit-lsp without a build, and an app target the index build never compiled still gets them. The compilation database pulled from `xcodebuild` output remains as a setting (`sourcekit_build_settings`). The index is the index build's (sourcekit-lsp doesn't prepare targets). Also semantic highlighting, signature help and inlay hints (Editor ▸ Inlay Hints), which Xcode doesn't have |
| Open Quickly (⇧⌘O), Quick Actions (⇧⌘A) | ✅ | Files and workspace symbols, `file:line` |
| Scheme and destination pickers | ✅ | `xcodebuild -list`, `simctl list`, `devicectl list devices` (again on USB attach and detach, window activation and opening the menu, else once a minute while the window is active), Any iOS Device; grouped as in Xcode and filtered by `SUPPORTED_PLATFORMS`; Debug/Release |
| Run a web app's `package.json` scripts with the same Run (⌘R), Stop (⌘.) and scheme menu (Xcode has none) | ✅ | A section per web app in the scheme menu and Choose Scheme…; the lockfile's package manager in a debug-area terminal with the login shell's `PATH`; beside the app's run (neither stops the other); the dev server's URL opened once and in the activity view; TypeScript, esbuild, Vite, ESLint and compiler errors as build issues; a plain folder runs too; over MCP as run targets. No JavaScript debugging or browser of zcode's own |
| Build, Run, Stop, Clean (⌘B ⌘R ⌘. ⇧⌘K) | ✅ | Simulator boot, install and launch; Mac executables; Swift packages. Stop ends the app however it was started (under lldb-dap too), as do quitting and closing the window |
| Issue navigator, ⌘' / ⌘" | ✅ | Live and build issues, de-duplicated, shown inline; Show Issues in Editor puts them all, with their lines and messages, in a multibuffer |
| Console / debug area (⇧⌘Y, ⌘K) | ✅ | App stdout/stderr via `simctl launch --console-pty` |
Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ in the same repository; editing it shouldn't need VS Code.
2. Linked editing of tag pairs (`textDocument/linkedEditingRange`), ESLint
fix-all on save, updating zcode's copies of the servers.
3. Run the web app: `package.json` scripts in a picker (`npm run dev` in a
terminal tab), the dev server's URL opened, its errors as issues.
terminal tab), the dev server's URL opened, its errors as issues. ✅

## Foundations

Expand Down
Loading