Merge remote-tracking branch 'upstream/main' into jacob/uucode

pull/8757/head
Jacob Sandlund 2025-09-25 08:43:18 -04:00
commit 4f2d80b08f
29 changed files with 1704 additions and 56 deletions

View File

@ -42,7 +42,7 @@ jobs:
/nix /nix
/zig /zig
- name: Setup Nix - name: Setup Nix
uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -89,7 +89,7 @@ jobs:
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable

View File

@ -34,7 +34,7 @@ jobs:
with: with:
# Important so that build number generation works # Important so that build number generation works
fetch-depth: 0 fetch-depth: 0
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -168,7 +168,7 @@ jobs:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -15,6 +15,7 @@ jobs:
- build-examples - build-examples
- build-flatpak - build-flatpak
- build-freebsd - build-freebsd
- build-libghostty-vt
- build-linux - build-linux
- build-linux-libghostty - build-linux-libghostty
- build-nix - build-nix
@ -79,7 +80,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -94,7 +95,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
dir: [zig-vt] dir: [c-vt, zig-vt]
name: Example ${{ matrix.dir }} name: Example ${{ matrix.dir }}
runs-on: namespace-profile-ghostty-sm runs-on: namespace-profile-ghostty-sm
needs: test needs: test
@ -113,7 +114,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -146,7 +147,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -180,7 +181,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -194,6 +195,48 @@ jobs:
zig build \ zig build \
-Dsnap -Dsnap
build-libghostty-vt:
strategy:
matrix:
target:
[
aarch64-macos,
x86_64-macos,
aarch64-linux,
x86_64-linux,
x86_64-windows,
]
runs-on: namespace-profile-ghostty-sm
needs: test
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
with:
path: |
/nix
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Build
run: |
nix develop -c zig build lib-vt \
-Dtarget=${{ matrix.target }} \
-Dsimd=false
build-linux: build-linux:
strategy: strategy:
fail-fast: false fail-fast: false
@ -216,7 +259,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -245,7 +288,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -278,7 +321,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -324,7 +367,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -536,7 +579,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -578,7 +621,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -626,7 +669,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -661,7 +704,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -725,7 +768,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -752,7 +795,7 @@ jobs:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -780,7 +823,7 @@ jobs:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -807,7 +850,7 @@ jobs:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -834,7 +877,7 @@ jobs:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -861,7 +904,7 @@ jobs:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -888,7 +931,7 @@ jobs:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -922,7 +965,7 @@ jobs:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -949,7 +992,7 @@ jobs:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -986,7 +1029,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -1074,7 +1117,7 @@ jobs:
/zig /zig
# Install Nix and use that to run our tests so our environment matches exactly. # Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 - uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -29,7 +29,7 @@ jobs:
/zig /zig
- name: Setup Nix - name: Setup Nix
uses: cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2 uses: cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -185,6 +185,7 @@
/po/he_IL.UTF-8.po @ghostty-org/he_IL /po/he_IL.UTF-8.po @ghostty-org/he_IL
/po/it_IT.UTF-8.po @ghostty-org/it_IT /po/it_IT.UTF-8.po @ghostty-org/it_IT
/po/zh_TW.UTF-8.po @ghostty-org/zh_TW /po/zh_TW.UTF-8.po @ghostty-org/zh_TW
/po/hr_HR.UTF-8.po @ghostty-org/hr_HR
# Packaging - Snap # Packaging - Snap
/snap/ @ghostty-org/snap /snap/ @ghostty-org/snap

28
Doxyfile Normal file
View File

@ -0,0 +1,28 @@
# Doxyfile 1.13.2
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "libghostty"
INPUT = include/ghostty/vt.h
INPUT_ENCODING = UTF-8
RECURSIVE = NO
#---------------------------------------------------------------------------
# HTML Output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = zig-out/share/ghostty/doc/libghostty
#---------------------------------------------------------------------------
# Man Output
#---------------------------------------------------------------------------
GENERATE_MAN = YES
MAN_OUTPUT = zig-out/share/man
MAN_EXTENSION = .3
#---------------------------------------------------------------------------
# Other Output
#---------------------------------------------------------------------------
GENERATE_LATEX = NO

View File

@ -31,6 +31,7 @@ pub fn build(b: *std.Build) !void {
// All our steps which we'll hook up later. The steps are shown // All our steps which we'll hook up later. The steps are shown
// up here just so that they are more self-documenting. // up here just so that they are more self-documenting.
const libvt_step = b.step("lib-vt", "Build libghostty-vt");
const run_step = b.step("run", "Run the app"); const run_step = b.step("run", "Run the app");
const run_valgrind_step = b.step( const run_valgrind_step = b.step(
"run-valgrind", "run-valgrind",
@ -86,7 +87,7 @@ pub fn build(b: *std.Build) !void {
check_step.dependOn(dist.install_step); check_step.dependOn(dist.install_step);
} }
// libghostty // libghostty (internal, big)
const libghostty_shared = try buildpkg.GhosttyLib.initShared( const libghostty_shared = try buildpkg.GhosttyLib.initShared(
b, b,
&deps, &deps,
@ -96,6 +97,14 @@ pub fn build(b: *std.Build) !void {
&deps, &deps,
); );
// libghostty-vt
const libghostty_vt_shared = try buildpkg.GhosttyLibVt.initShared(
b,
&mod,
);
libghostty_vt_shared.install(libvt_step);
libghostty_vt_shared.install(b.getInstallStep());
// Helpgen // Helpgen
if (config.emit_helpgen) deps.help_strings.install(); if (config.emit_helpgen) deps.help_strings.install();

17
example/c-vt/README.md Normal file
View File

@ -0,0 +1,17 @@
# Example: `ghostty-vt` C Program
This contains a simple example of how to use the `ghostty-vt` C library
with a C program.
This uses a `build.zig` and `Zig` to build the C program so that we
can reuse a lot of our build logic and depend directly on our source
tree, but Ghostty emits a standard C library that can be used with any
C tooling.
## Usage
Run the program:
```shell-session
zig build run
```

42
example/c-vt/build.zig Normal file
View File

@ -0,0 +1,42 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const run_step = b.step("run", "Run the app");
const exe_mod = b.createModule(.{
.target = target,
.optimize = optimize,
});
exe_mod.addCSourceFiles(.{
.root = b.path("src"),
.files = &.{"main.c"},
});
// You'll want to use a lazy dependency here so that ghostty is only
// downloaded if you actually need it.
if (b.lazyDependency("ghostty", .{
// Setting simd to false will force a pure static build that
// doesn't even require libc, but it has a significant performance
// penalty. If your embedding app requires libc anyway, you should
// always keep simd enabled.
// .simd = false,
})) |dep| {
exe_mod.linkLibrary(dep.artifact("ghostty-vt"));
}
// Exe
const exe = b.addExecutable(.{
.name = "c_vt",
.root_module = exe_mod,
});
b.installArtifact(exe);
// Run
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
run_step.dependOn(&run_cmd.step);
}

View File

@ -0,0 +1,24 @@
.{
.name = .c_vt,
.version = "0.0.0",
.fingerprint = 0x413a8529b1255f9a,
.minimum_zig_version = "0.14.1",
.dependencies = .{
// Ghostty dependency. In reality, you'd probably use a URL-based
// dependency like the one showed (and commented out) below this one.
// We use a path dependency here for simplicity and to ensure our
// examples always test against the source they're bundled with.
.ghostty = .{ .path = "../../" },
// Example of what a URL-based dependency looks like:
// .ghostty = .{
// .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz",
// .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s",
// },
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

11
example/c-vt/src/main.c Normal file
View File

@ -0,0 +1,11 @@
#include <stddef.h>
#include <ghostty/vt.h>
int main() {
GhosttyOscParser parser;
if (ghostty_osc_new(NULL, &parser) != GHOSTTY_SUCCESS) {
return 1;
}
ghostty_osc_free(parser);
return 0;
}

221
include/ghostty/vt.h Normal file
View File

@ -0,0 +1,221 @@
/**
* @file vt.h
*
* libghostty-vt - Virtual terminal sequence parsing library
*
* This library provides functionality for parsing and handling terminal
* escape sequences as well as maintaining terminal state such as styles,
* cursor position, screen, scrollback, and more.
*
* WARNING: This is an incomplete, work-in-progress API. It is not yet
* stable and is definitely going to change.
*/
#ifndef GHOSTTY_VT_H
#define GHOSTTY_VT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//-------------------------------------------------------------------
// Types
/**
* Opaque handle to an OSC parser instance.
*
* This handle represents an OSC (Operating System Command) parser that can
* be used to parse the contents of OSC sequences. This isn't a full VT
* parser; it is only the OSC parser component. This is useful if you have
* a parser already and want to only extract and handle OSC sequences.
*/
typedef struct GhosttyOscParser *GhosttyOscParser;
/**
* Result codes for libghostty-vt operations.
*/
typedef enum {
/** Operation completed successfully */
GHOSTTY_SUCCESS = 0,
/** Operation failed due to failed allocation */
GHOSTTY_OUT_OF_MEMORY = -1,
} GhosttyResult;
//-------------------------------------------------------------------
// Allocator Interface
/**
* Function table for custom memory allocator operations.
*
* This vtable defines the interface for a custom memory allocator. All
* function pointers must be valid and non-NULL.
*
* If you're not going to use a custom allocator, you can ignore all of
* this. All functions that take an allocator pointer allow NULL to use a
* default allocator.
*
* The interface is based on the Zig allocator interface. I'll say up front
* that it is easy to look at this interface and think "wow, this is really
* overcomplicated". The reason for this complexity is well thought out by
* the Zig folks, and it enables a diverse set of allocation strategies
* as shown by the Zig ecosystem. As a consolation, please note that many
* of the arguments are only needed for advanced use cases and can be
* safely ignored in simple implementations. For example, if you look at
* the Zig implementation of the libc allocator in `lib/std/heap.zig`
* (search for CAllocator), you'll see it is very simple.
*
* We chose to align with the Zig allocator interface because:
*
* 1. It is a proven interface that serves a wide variety of use cases
* in the real world via the Zig ecosystem. It's shown to work.
*
* 2. Our core implementation itself is Zig, and this lets us very
* cheaply and easily convert between C and Zig allocators.
*
* NOTE(mitchellh): In the future, we can have default implementations of
* resize/remap and allow those to be null.
*/
typedef struct {
/**
* Return a pointer to `len` bytes with specified `alignment`, or return
* `NULL` indicating the allocation failed.
*
* @param ctx The allocator context
* @param len Number of bytes to allocate
* @param alignment Required alignment for the allocation. Guaranteed to
* be a power of two between 1 and 16 inclusive.
* @param ret_addr First return address of the allocation call stack (0 if not provided)
* @return Pointer to allocated memory, or NULL if allocation failed
*/
void* (*alloc)(void *ctx, size_t len, uint8_t alignment, uintptr_t ret_addr);
/**
* Attempt to expand or shrink memory in place.
*
* `memory_len` must equal the length requested from the most recent
* successful call to `alloc`, `resize`, or `remap`. `alignment` must
* equal the same value that was passed as the `alignment` parameter to
* the original `alloc` call.
*
* `new_len` must be greater than zero.
*
* @param ctx The allocator context
* @param memory Pointer to the memory block to resize
* @param memory_len Current size of the memory block
* @param alignment Alignment (must match original allocation)
* @param new_len New requested size
* @param ret_addr First return address of the allocation call stack (0 if not provided)
* @return true if resize was successful in-place, false if relocation would be required
*/
bool (*resize)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, size_t new_len, uintptr_t ret_addr);
/**
* Attempt to expand or shrink memory, allowing relocation.
*
* `memory_len` must equal the length requested from the most recent
* successful call to `alloc`, `resize`, or `remap`. `alignment` must
* equal the same value that was passed as the `alignment` parameter to
* the original `alloc` call.
*
* A non-`NULL` return value indicates the resize was successful. The
* allocation may have same address, or may have been relocated. In either
* case, the allocation now has size of `new_len`. A `NULL` return value
* indicates that the resize would be equivalent to allocating new memory,
* copying the bytes from the old memory, and then freeing the old memory.
* In such case, it is more efficient for the caller to perform the copy.
*
* `new_len` must be greater than zero.
*
* @param ctx The allocator context
* @param memory Pointer to the memory block to remap
* @param memory_len Current size of the memory block
* @param alignment Alignment (must match original allocation)
* @param new_len New requested size
* @param ret_addr First return address of the allocation call stack (0 if not provided)
* @return Pointer to resized memory (may be relocated), or NULL if manual copy is needed
*/
void* (*remap)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, size_t new_len, uintptr_t ret_addr);
/**
* Free and invalidate a region of memory.
*
* `memory_len` must equal the length requested from the most recent
* successful call to `alloc`, `resize`, or `remap`. `alignment` must
* equal the same value that was passed as the `alignment` parameter to
* the original `alloc` call.
*
* @param ctx The allocator context
* @param memory Pointer to the memory block to free
* @param memory_len Size of the memory block
* @param alignment Alignment (must match original allocation)
* @param ret_addr First return address of the allocation call stack (0 if not provided)
*/
void (*free)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, uintptr_t ret_addr);
} GhosttyAllocatorVtable;
/**
* Custom memory allocator.
*
* For functions that take an allocator pointer, a NULL pointer indicates
* that the default allocator should be used. The default allocator will
* be libc malloc/free if we're linking to libc. If libc isn't linked,
* a custom allocator is used (currently Zig's SMP allocator).
*
* Usage example:
* @code
* GhosttyAllocator allocator = {
* .vtable = &my_allocator_vtable,
* .ctx = my_allocator_state
* };
* @endcode
*/
typedef struct {
/**
* Opaque context pointer passed to all vtable functions.
* This allows the allocator implementation to maintain state
* or reference external resources needed for memory management.
*/
void *ctx;
/**
* Pointer to the allocator's vtable containing function pointers
* for memory operations (alloc, resize, remap, free).
*/
const GhosttyAllocatorVtable *vtable;
} GhosttyAllocator;
//-------------------------------------------------------------------
// Functions
/**
* Create a new OSC parser instance.
*
* Creates a new OSC (Operating System Command) parser using the provided
* allocator. The parser must be freed using ghostty_vt_osc_free() when
* no longer needed.
*
* @param allocator Pointer to the allocator to use for memory management, or NULL to use the default allocator
* @param parser Pointer to store the created parser handle
* @return GHOSTTY_SUCCESS on success, or an error code on failure
*/
GhosttyResult ghostty_osc_new(const GhosttyAllocator *allocator, GhosttyOscParser *parser);
/**
* Free an OSC parser instance.
*
* Releases all resources associated with the OSC parser. After this call,
* the parser handle becomes invalid and must not be used.
*
* @param parser The parser handle to free (may be NULL)
*/
void ghostty_osc_free(GhosttyOscParser parser);
#ifdef __cplusplus
}
#endif
#endif /* GHOSTTY_VT_H */

View File

@ -3,6 +3,7 @@
lib, lib,
stdenv, stdenv,
bashInteractive, bashInteractive,
doxygen,
nushell, nushell,
appstream, appstream,
flatpak-builder, flatpak-builder,
@ -89,6 +90,7 @@ in
packages = packages =
[ [
# For builds # For builds
doxygen
jq jq
llvmPackages_latest.llvm llvmPackages_latest.llvm
minisign minisign

321
po/hr_HR.UTF-8.po Normal file
View File

@ -0,0 +1,321 @@
# Croatian translations for com.mitchellh.ghostty package
# Hrvatski prijevod za paket com.mitchellh.ghostty.
# Copyright (C) 2025 "Mitchell Hashimoto, Ghostty contributors"
# This file is distributed under the same license as the com.mitchellh.ghostty package.
# Filip <filipm7@protonmail.com>, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
"PO-Revision-Date: 2025-09-16 17:47+0200\n"
"Last-Translator: Filip7 <filipm7@protonmail.com>\n"
"Language-Team: Croatian <lokalizacija@linux.hr>\n"
"Language: hr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
msgid "Change Terminal Title"
msgstr "Promijeni naslov terminala"
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
msgid "Leave blank to restore the default title."
msgstr "Ostavi prazno za povratak zadanog naslova."
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/ui/1.2/ccw-paste.blp:10
#: src/apprt/gtk/CloseDialog.zig:44
msgid "Cancel"
msgstr "Otkaži"
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
msgid "OK"
msgstr "OK"
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5
msgid "Configuration Errors"
msgstr "Greške u postavkama"
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
msgid ""
"One or more configuration errors were found. Please review the errors below, "
"and either reload your configuration or ignore these errors."
msgstr ""
"Pronađene su jedna ili više grešaka u postavkama. Pregledaj niže navedene greške"
"te ponovno učitaj postavke ili zanemari ove greške."
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9
msgid "Ignore"
msgstr "Zanemari"
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10
msgid "Reload Configuration"
msgstr "Ponovno učitaj postavke"
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50
msgid "Split Up"
msgstr "Podijeli gore"
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55
msgid "Split Down"
msgstr "Podijeli dolje"
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60
msgid "Split Left"
msgstr "Podijeli lijevo"
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65
msgid "Split Right"
msgstr "Podijeli desno"
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
msgid "Execute a command…"
msgstr "Izvrši naredbu…"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
msgid "Copy"
msgstr "Kopiraj"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 src/apprt/gtk/ui/1.2/ccw-paste.blp:11
msgid "Paste"
msgstr "Zalijepi"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73
msgid "Clear"
msgstr "Očisti"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78
msgid "Reset"
msgstr "Resetiraj"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42
msgid "Split"
msgstr "Podijeli"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45
msgid "Change Title…"
msgstr "Promijeni naslov…"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59
msgid "Tab"
msgstr "Kartica"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30
#: src/apprt/gtk/Window.zig:265
msgid "New Tab"
msgstr "Nova kartica"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35
msgid "Close Tab"
msgstr "Zatvori karticu"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73
msgid "Window"
msgstr "Prozor"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18
msgid "New Window"
msgstr "Novi prozor"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23
msgid "Close Window"
msgstr "Zatvori prozor"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
msgid "Config"
msgstr "Postavke"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
msgid "Open Configuration"
msgstr "Otvori postavke"
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
msgid "Command Palette"
msgstr "Paleta naredbi"
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
msgid "Terminal Inspector"
msgstr "Inspektor terminala"
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
#: src/apprt/gtk/Window.zig:1038
msgid "About Ghostty"
msgstr "O Ghosttyju"
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112
msgid "Quit"
msgstr "Izađi"
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:6
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:6
msgid "Authorize Clipboard Access"
msgstr "Dopusti pristup međuspremniku"
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7
msgid ""
"An application is attempting to read from the clipboard. The current "
"clipboard contents are shown below."
msgstr ""
"Program pokušava pročitati vrijednost međuspremnika. Trenutna"
"vrijednost međuspremnika je prikazana niže."
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10
msgid "Deny"
msgstr "Odbij"
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:11
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:11
msgid "Allow"
msgstr "Dopusti"
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
msgid "Remember choice for this split"
msgstr "Zapamti izbor za ovu podjelu"
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
msgid "Reload configuration to show this prompt again"
msgstr "Ponovno učitaj postavke za prikaz ovog upita"
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
msgid ""
"An application is attempting to write to the clipboard. The current "
"clipboard contents are shown below."
msgstr ""
"Aplikacija pokušava pisati u međuspremnik. Trenutačna vrijednost "
"međuspremnika prikazana je niže."
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
msgid "Warning: Potentially Unsafe Paste"
msgstr "Upozorenje: Potencijalno opasno lijepljenje"
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 src/apprt/gtk/ui/1.2/ccw-paste.blp:7
msgid ""
"Pasting this text into the terminal may be dangerous as it looks like some "
"commands may be executed."
msgstr ""
"Lijepljenje ovog teksta u terminal može biti opasno jer se čini da "
"neke naredbe mogu biti izvršene."
#: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2531
msgid "Close"
msgstr "Zatvori"
#: src/apprt/gtk/CloseDialog.zig:87
msgid "Quit Ghostty?"
msgstr "Zatvori Ghostty?"
#: src/apprt/gtk/CloseDialog.zig:88
msgid "Close Window?"
msgstr "Zatvori prozor?"
#: src/apprt/gtk/CloseDialog.zig:89
msgid "Close Tab?"
msgstr "Zatvori karticu?"
#: src/apprt/gtk/CloseDialog.zig:90
msgid "Close Split?"
msgstr "Zatvori podjelu?"
#: src/apprt/gtk/CloseDialog.zig:96
msgid "All terminal sessions will be terminated."
msgstr "Sve sesije terminala će biti prekinute."
#: src/apprt/gtk/CloseDialog.zig:97
msgid "All terminal sessions in this window will be terminated."
msgstr "Sve sesije terminala u ovom prozoru će biti prekinute."
#: src/apprt/gtk/CloseDialog.zig:98
msgid "All terminal sessions in this tab will be terminated."
msgstr "Sve sesije terminala u ovoj kartici će biti prekinute."
#: src/apprt/gtk/CloseDialog.zig:99
msgid "The currently running process in this split will be terminated."
msgstr "Pokrenuti procesi u ovom odjeljku će biti prekinuti."
#: src/apprt/gtk/Surface.zig:1266
msgid "Copied to clipboard"
msgstr "Kopirano u međuspremnik"
#: src/apprt/gtk/Surface.zig:1268
msgid "Cleared clipboard"
msgstr "Očišćen međuspremnik"
#: src/apprt/gtk/Surface.zig:2525
msgid "Command succeeded"
msgstr "Naredba je uspjela"
#: src/apprt/gtk/Surface.zig:2527
msgid "Command failed"
msgstr "Naredba nije uspjela"
#: src/apprt/gtk/Window.zig:216
msgid "Main Menu"
msgstr "Glavni izbornik"
#: src/apprt/gtk/Window.zig:239
msgid "View Open Tabs"
msgstr "Pregledaj otvorene kartice"
#: src/apprt/gtk/Window.zig:266
msgid "New Split"
msgstr "Nova podjela"
#: src/apprt/gtk/Window.zig:329
msgid ""
"⚠️ You're running a debug build of Ghostty! Performance will be degraded."
msgstr ""
"⚠️ Pokrenuta je debug verzija Ghosttyja! Performanse će biti smanjene."
#: src/apprt/gtk/Window.zig:775
msgid "Reloaded the configuration"
msgstr "Ponovno učitane postavke"
#: src/apprt/gtk/Window.zig:1019
msgid "Ghostty Developers"
msgstr "Razvijatelji Ghosttyja"
#: src/apprt/gtk/inspector.zig:144
msgid "Ghostty: Terminal Inspector"
msgstr "Ghostty: inspektor terminala"

View File

@ -809,6 +809,10 @@ pub const Application = extern struct {
const writer = buf.writer(alloc); const writer = buf.writer(alloc);
// Load standard css first as it can override some of the user configured styling.
try loadRuntimeCss414(config, &writer);
try loadRuntimeCss416(config, &writer);
const unfocused_fill: CoreConfig.Color = config.@"unfocused-split-fill" orelse config.background; const unfocused_fill: CoreConfig.Color = config.@"unfocused-split-fill" orelse config.background;
try writer.print( try writer.print(
@ -847,9 +851,6 @@ pub const Application = extern struct {
, .{ .font_family = font_family }); , .{ .font_family = font_family });
} }
try loadRuntimeCss414(config, &writer);
try loadRuntimeCss416(config, &writer);
// ensure that we have a sentinel // ensure that we have a sentinel
try writer.writeByte(0); try writer.writeByte(0);

View File

@ -0,0 +1,87 @@
const GhosttyLibVt = @This();
const std = @import("std");
const RunStep = std.Build.Step.Run;
const Config = @import("Config.zig");
const GhosttyZig = @import("GhosttyZig.zig");
const SharedDeps = @import("SharedDeps.zig");
const LibtoolStep = @import("LibtoolStep.zig");
const LipoStep = @import("LipoStep.zig");
/// The step that generates the file.
step: *std.Build.Step,
/// The artifact result
artifact: *std.Build.Step.InstallArtifact,
/// The final library file
output: std.Build.LazyPath,
dsym: ?std.Build.LazyPath,
pkg_config: std.Build.LazyPath,
pub fn initShared(
b: *std.Build,
zig: *const GhosttyZig,
) !GhosttyLibVt {
const target = zig.vt.resolved_target.?;
const lib = b.addSharedLibrary(.{
.name = "ghostty-vt",
.root_module = zig.vt,
});
lib.installHeader(
b.path("include/ghostty/vt.h"),
"ghostty/vt.h",
);
// Get our debug symbols
const dsymutil: ?std.Build.LazyPath = dsymutil: {
if (!target.result.os.tag.isDarwin()) {
break :dsymutil null;
}
const dsymutil = RunStep.create(b, "dsymutil");
dsymutil.addArgs(&.{"dsymutil"});
dsymutil.addFileArg(lib.getEmittedBin());
dsymutil.addArgs(&.{"-o"});
const output = dsymutil.addOutputFileArg("libghostty-vt.dSYM");
break :dsymutil output;
};
// pkg-config
const pc: std.Build.LazyPath = pc: {
const wf = b.addWriteFiles();
break :pc wf.add("libghostty-vt.pc", b.fmt(
\\prefix={s}
\\includedir=${{prefix}}/include
\\libdir=${{prefix}}/lib
\\
\\Name: libghostty-vt
\\URL: https://github.com/ghostty-org/ghostty
\\Description: Ghostty VT library
\\Version: 0.1.0
\\Cflags: -I${{includedir}}
\\Libs: -L${{libdir}} -lghostty-vt
, .{b.install_prefix}));
};
return .{
.step = &lib.step,
.artifact = b.addInstallArtifact(lib, .{}),
.output = lib.getEmittedBin(),
.dsym = dsymutil,
.pkg_config = pc,
};
}
pub fn install(
self: *const GhosttyLibVt,
step: *std.Build.Step,
) void {
const b = step.owner;
step.dependOn(&self.artifact.step);
step.dependOn(&b.addInstallFileWithDir(
self.pkg_config,
.prefix,
"share/pkgconfig/libghostty-vt.pc",
).step);
}

View File

@ -1,6 +1,8 @@
const SharedDeps = @This(); const SharedDeps = @This();
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const Config = @import("Config.zig"); const Config = @import("Config.zig");
const HelpStrings = @import("HelpStrings.zig"); const HelpStrings = @import("HelpStrings.zig");
const MetallibStep = @import("MetallibStep.zig"); const MetallibStep = @import("MetallibStep.zig");
@ -114,6 +116,19 @@ pub fn add(
var static_libs = LazyPathList.init(b.allocator); var static_libs = LazyPathList.init(b.allocator);
errdefer static_libs.deinit(); errdefer static_libs.deinit();
// WARNING: This is a hack!
// If we're cross-compiling to Darwin then we don't add any deps.
// We don't support cross-compiling to Darwin but due to the way
// lazy dependencies work with Zig, we call this function. So we just
// bail. The build will fail but the build would've failed anyways.
// And this lets other non-platform-specific targets like `lib-vt`
// cross-compile properly.
if (!builtin.target.os.tag.isDarwin() and
self.config.target.result.os.tag.isDarwin())
{
return static_libs;
}
// Every exe gets build options populated // Every exe gets build options populated
step.root_module.addOptions("build_options", self.options); step.root_module.addOptions("build_options", self.options);

View File

@ -13,6 +13,7 @@ pub const GhosttyDocs = @import("GhosttyDocs.zig");
pub const GhosttyExe = @import("GhosttyExe.zig"); pub const GhosttyExe = @import("GhosttyExe.zig");
pub const GhosttyFrameData = @import("GhosttyFrameData.zig"); pub const GhosttyFrameData = @import("GhosttyFrameData.zig");
pub const GhosttyLib = @import("GhosttyLib.zig"); pub const GhosttyLib = @import("GhosttyLib.zig");
pub const GhosttyLibVt = @import("GhosttyLibVt.zig");
pub const GhosttyResources = @import("GhosttyResources.zig"); pub const GhosttyResources = @import("GhosttyResources.zig");
pub const GhosttyI18n = @import("GhosttyI18n.zig"); pub const GhosttyI18n = @import("GhosttyI18n.zig");
pub const GhosttyXcodebuild = @import("GhosttyXcodebuild.zig"); pub const GhosttyXcodebuild = @import("GhosttyXcodebuild.zig");

424
src/cli/CommaSplitter.zig Normal file
View File

@ -0,0 +1,424 @@
//! Iterator to split a string into fields by commas, taking into account
//! quotes and escapes.
//!
//! Supports the same escapes as in Zig literal strings.
//!
//! Quotes must begin and end with a double quote (`"`). It is an error to not
//! end a quote that was begun. To include a double quote inside a quote (or to
//! not have a double quote start a quoted section) escape it with a backslash.
//!
//! Single quotes (`'`) are not special, they do not begin a quoted block.
//!
//! Zig multiline string literals are NOT supported.
//!
//! Quotes and escapes are not stripped or decoded, that must be handled as a
//! separate step!
const CommaSplitter = @This();
pub const Error = error{
UnclosedQuote,
UnfinishedEscape,
IllegalEscape,
};
/// the string that we are splitting
str: []const u8,
/// how much of the string has been consumed so far
index: usize,
/// initialize a splitter with the given string
pub fn init(str: []const u8) CommaSplitter {
return .{
.str = str,
.index = 0,
};
}
/// return the next field, null if no more fields
pub fn next(self: *CommaSplitter) Error!?[]const u8 {
if (self.index >= self.str.len) return null;
// where the current field starts
const start = self.index;
// state of state machine
const State = enum {
normal,
quoted,
escape,
hexescape,
unicodeescape,
};
// keep track of the state to return to when done processing an escape
// sequence.
var last: State = .normal;
// used to count number of digits seen in a hex escape
var hexescape_digits: usize = 0;
// sub-state of parsing hex escapes
var unicodeescape_state: enum {
start,
digits,
} = .start;
// number of digits in a unicode escape seen so far
var unicodeescape_digits: usize = 0;
// accumulator for value of unicode escape
var unicodeescape_value: usize = 0;
loop: switch (State.normal) {
.normal => {
if (self.index >= self.str.len) return self.str[start..];
switch (self.str[self.index]) {
',' => {
self.index += 1;
return self.str[start .. self.index - 1];
},
'"' => {
self.index += 1;
continue :loop .quoted;
},
'\\' => {
self.index += 1;
last = .normal;
continue :loop .escape;
},
else => {
self.index += 1;
continue :loop .normal;
},
}
},
.quoted => {
if (self.index >= self.str.len) return error.UnclosedQuote;
switch (self.str[self.index]) {
'"' => {
self.index += 1;
continue :loop .normal;
},
'\\' => {
self.index += 1;
last = .quoted;
continue :loop .escape;
},
else => {
self.index += 1;
continue :loop .quoted;
},
}
},
.escape => {
if (self.index >= self.str.len) return error.UnfinishedEscape;
switch (self.str[self.index]) {
'n', 'r', 't', '\\', '\'', '"' => {
self.index += 1;
continue :loop last;
},
'x' => {
self.index += 1;
hexescape_digits = 0;
continue :loop .hexescape;
},
'u' => {
self.index += 1;
unicodeescape_state = .start;
unicodeescape_digits = 0;
unicodeescape_value = 0;
continue :loop .unicodeescape;
},
else => return error.IllegalEscape,
}
},
.hexescape => {
if (self.index >= self.str.len) return error.UnfinishedEscape;
switch (self.str[self.index]) {
'0'...'9', 'a'...'f', 'A'...'F' => {
self.index += 1;
hexescape_digits += 1;
if (hexescape_digits == 2) continue :loop last;
continue :loop .hexescape;
},
else => return error.IllegalEscape,
}
},
.unicodeescape => {
if (self.index >= self.str.len) return error.UnfinishedEscape;
switch (unicodeescape_state) {
.start => {
switch (self.str[self.index]) {
'{' => {
self.index += 1;
unicodeescape_value = 0;
unicodeescape_state = .digits;
continue :loop .unicodeescape;
},
else => return error.IllegalEscape,
}
},
.digits => {
switch (self.str[self.index]) {
'}' => {
self.index += 1;
if (unicodeescape_digits == 0) return error.IllegalEscape;
continue :loop last;
},
'0'...'9' => |d| {
self.index += 1;
unicodeescape_digits += 1;
unicodeescape_value <<= 4;
unicodeescape_value += d - '0';
},
'a'...'f' => |d| {
self.index += 1;
unicodeescape_digits += 1;
unicodeescape_value <<= 4;
unicodeescape_value += d - 'a';
},
'A'...'F' => |d| {
self.index += 1;
unicodeescape_digits += 1;
unicodeescape_value <<= 4;
unicodeescape_value += d - 'A';
},
else => return error.IllegalEscape,
}
if (unicodeescape_value > 0x10ffff) return error.IllegalEscape;
continue :loop .unicodeescape;
},
}
},
}
}
/// Return any remaining string data, whether it has a comma or not.
pub fn rest(self: *CommaSplitter) ?[]const u8 {
if (self.index >= self.str.len) return null;
defer self.index = self.str.len;
return self.str[self.index..];
}
test "splitter 1" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("a,b,c");
try testing.expectEqualStrings("a", (try s.next()).?);
try testing.expectEqualStrings("b", (try s.next()).?);
try testing.expectEqualStrings("c", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 2" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("");
try testing.expect(null == try s.next());
}
test "splitter 3" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("a");
try testing.expectEqualStrings("a", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 4" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\x5a");
try testing.expectEqualStrings("\\x5a", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 5" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("'a',b");
try testing.expectEqualStrings("'a'", (try s.next()).?);
try testing.expectEqualStrings("b", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 6" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("'a,b',c");
try testing.expectEqualStrings("'a", (try s.next()).?);
try testing.expectEqualStrings("b'", (try s.next()).?);
try testing.expectEqualStrings("c", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 7" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\"a,b\",c");
try testing.expectEqualStrings("\"a,b\"", (try s.next()).?);
try testing.expectEqualStrings("c", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 8" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init(" a , b ");
try testing.expectEqualStrings(" a ", (try s.next()).?);
try testing.expectEqualStrings(" b ", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 9" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\x");
try testing.expectError(error.UnfinishedEscape, s.next());
}
test "splitter 10" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\x5");
try testing.expectError(error.UnfinishedEscape, s.next());
}
test "splitter 11" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\u");
try testing.expectError(error.UnfinishedEscape, s.next());
}
test "splitter 12" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\u{");
try testing.expectError(error.UnfinishedEscape, s.next());
}
test "splitter 13" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\u{}");
try testing.expectError(error.IllegalEscape, s.next());
}
test "splitter 14" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\u{h1}");
try testing.expectError(error.IllegalEscape, s.next());
}
test "splitter 15" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\u{10ffff}");
try testing.expectEqualStrings("\\u{10ffff}", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 16" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\u{110000}");
try testing.expectError(error.IllegalEscape, s.next());
}
test "splitter 17" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\d");
try testing.expectError(error.IllegalEscape, s.next());
}
test "splitter 18" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\\n\\r\\t\\\"\\'\\\\");
try testing.expectEqualStrings("\\n\\r\\t\\\"\\'\\\\", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 19" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\"abc'def'ghi\"");
try testing.expectEqualStrings("\"abc'def'ghi\"", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 20" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("\",\",abc");
try testing.expectEqualStrings("\",\"", (try s.next()).?);
try testing.expectEqualStrings("abc", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 21" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("'a','b', 'c'");
try testing.expectEqualStrings("'a'", (try s.next()).?);
try testing.expectEqualStrings("'b'", (try s.next()).?);
try testing.expectEqualStrings(" 'c'", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 22" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("abc\"def");
try testing.expectError(error.UnclosedQuote, s.next());
}
test "splitter 23" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("title:\"Focus Split: Up\",description:\"Focus the split above, if it exists.\",action:goto_split:up");
try testing.expectEqualStrings("title:\"Focus Split: Up\"", (try s.next()).?);
try testing.expectEqualStrings("description:\"Focus the split above, if it exists.\"", (try s.next()).?);
try testing.expectEqualStrings("action:goto_split:up", (try s.next()).?);
try testing.expect(null == try s.next());
}
test "splitter 24" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("a,b,c,def");
try testing.expectEqualStrings("a", (try s.next()).?);
try testing.expectEqualStrings("b", (try s.next()).?);
try testing.expectEqualStrings("c,def", s.rest().?);
try testing.expect(null == try s.next());
}
test "splitter 25" {
const std = @import("std");
const testing = std.testing;
var s: CommaSplitter = .init("a,\\u{10,df}");
try testing.expectEqualStrings("a", (try s.next()).?);
try testing.expectError(error.IllegalEscape, s.next());
}

View File

@ -7,6 +7,7 @@ const diags = @import("diagnostics.zig");
const internal_os = @import("../os/main.zig"); const internal_os = @import("../os/main.zig");
const Diagnostic = diags.Diagnostic; const Diagnostic = diags.Diagnostic;
const DiagnosticList = diags.DiagnosticList; const DiagnosticList = diags.DiagnosticList;
const CommaSplitter = @import("CommaSplitter.zig");
const log = std.log.scoped(.cli); const log = std.log.scoped(.cli);
@ -527,24 +528,31 @@ pub fn parseAutoStruct(comptime T: type, alloc: Allocator, v: []const u8) !T {
const FieldSet = std.StaticBitSet(info.fields.len); const FieldSet = std.StaticBitSet(info.fields.len);
var fields_set: FieldSet = .initEmpty(); var fields_set: FieldSet = .initEmpty();
// We split each value by "," // We split each value by "," allowing for quoting and escaping.
var iter = std.mem.splitSequence(u8, v, ","); var iter: CommaSplitter = .init(v);
loop: while (iter.next()) |entry| { loop: while (try iter.next()) |entry| {
// Find the key/value, trimming whitespace. The value may be quoted // Find the key/value, trimming whitespace. The value may be quoted
// which we strip the quotes from. // which we strip the quotes from.
const idx = mem.indexOf(u8, entry, ":") orelse return error.InvalidValue; const idx = mem.indexOf(u8, entry, ":") orelse return error.InvalidValue;
const key = std.mem.trim(u8, entry[0..idx], whitespace); const key = std.mem.trim(u8, entry[0..idx], whitespace);
// used if we need to decode a double-quoted string.
var buf: std.ArrayListUnmanaged(u8) = .empty;
defer buf.deinit(alloc);
const value = value: { const value = value: {
var value = std.mem.trim(u8, entry[idx + 1 ..], whitespace); const value = std.mem.trim(u8, entry[idx + 1 ..], whitespace);
// Detect a quoted string. // Detect a quoted string.
if (value.len >= 2 and if (value.len >= 2 and
value[0] == '"' and value[0] == '"' and
value[value.len - 1] == '"') value[value.len - 1] == '"')
{ {
// Trim quotes since our CLI args processor expects // Decode a double-quoted string as a Zig string literal.
// quotes to already be gone. const writer = buf.writer(alloc);
value = value[1 .. value.len - 1]; const parsed = try std.zig.string_literal.parseWrite(writer, value);
if (parsed == .failure) return error.InvalidValue;
break :value buf.items;
} }
break :value value; break :value value;

View File

@ -2363,9 +2363,21 @@ keybind: Keybinds = .{},
/// (`:`), and then the specified value. The syntax for actions is identical /// (`:`), and then the specified value. The syntax for actions is identical
/// to the one for keybind actions. Whitespace in between fields is ignored. /// to the one for keybind actions. Whitespace in between fields is ignored.
/// ///
/// If you need to embed commas or any other special characters in the values,
/// enclose the value in double quotes and it will be interpreted as a Zig
/// string literal. This is also useful for including whitespace at the
/// beginning or the end of a value. See the
/// [Zig documentation](https://ziglang.org/documentation/master/#Escape-Sequences)
/// for more information on string literals. Note that multiline string literals
/// are not supported.
///
/// Double quotes can not be used around the field names.
///
/// ```ini /// ```ini
/// command-palette-entry = title:Reset Font Style, action:csi:0m /// command-palette-entry = title:Reset Font Style, action:csi:0m
/// command-palette-entry = title:Crash on Main Thread,description:Causes a crash on the main (UI) thread.,action:crash:main /// command-palette-entry = title:Crash on Main Thread,description:Causes a crash on the main (UI) thread.,action:crash:main
/// command-palette-entry = title:Focus Split: Right,description:"Focus the split to the right, if it exists.",action:goto_split:right
/// command-palette-entry = title:"Ghostty",description:"Add a little Ghostty to your terminal.",action:"text:\xf0\x9f\x91\xbb"
/// ``` /// ```
/// ///
/// By default, the command palette is preloaded with most actions that might /// By default, the command palette is preloaded with most actions that might
@ -3351,7 +3363,7 @@ pub fn loadOptionalFile(
fn writeConfigTemplate(path: []const u8) !void { fn writeConfigTemplate(path: []const u8) !void {
log.info("creating template config file: path={s}", .{path}); log.info("creating template config file: path={s}", .{path});
if (std.fs.path.dirname(path)) |dir_path| { if (std.fs.path.dirname(path)) |dir_path| {
try std.fs.makeDirAbsolute(dir_path); try std.fs.cwd().makePath(dir_path);
} }
const file = try std.fs.createFileAbsolute(path, .{}); const file = try std.fs.createFileAbsolute(path, .{});
defer file.close(); defer file.close();
@ -7029,18 +7041,24 @@ pub const RepeatableCommand = struct {
return; return;
} }
var buf: [4096]u8 = undefined;
for (self.value.items) |item| { for (self.value.items) |item| {
const str = if (item.description.len > 0) std.fmt.bufPrint( var buf: [4096]u8 = undefined;
&buf, var fbs = std.io.fixedBufferStream(&buf);
"title:{s},description:{s},action:{}", var writer = fbs.writer();
.{ item.title, item.description, item.action },
) else std.fmt.bufPrint( writer.writeAll("title:\"") catch return error.OutOfMemory;
&buf, std.zig.stringEscape(item.title, "", .{}, writer) catch return error.OutOfMemory;
"title:{s},action:{}", writer.writeAll("\"") catch return error.OutOfMemory;
.{ item.title, item.action },
); if (item.description.len > 0) {
try formatter.formatEntry([]const u8, str catch return error.OutOfMemory); writer.writeAll(",description:\"") catch return error.OutOfMemory;
std.zig.stringEscape(item.description, "", .{}, writer) catch return error.OutOfMemory;
writer.writeAll("\"") catch return error.OutOfMemory;
}
writer.print(",action:\"{}\"", .{item.action}) catch return error.OutOfMemory;
try formatter.formatEntry([]const u8, fbs.getWritten());
} }
} }
@ -7106,7 +7124,7 @@ pub const RepeatableCommand = struct {
var list: RepeatableCommand = .{}; var list: RepeatableCommand = .{};
try list.parseCLI(alloc, "title:Bobr, action:text:Bober"); try list.parseCLI(alloc, "title:Bobr, action:text:Bober");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
try std.testing.expectEqualSlices(u8, "a = title:Bobr,action:text:Bober\n", buf.items); try std.testing.expectEqualSlices(u8, "a = title:\"Bobr\",action:\"text:Bober\"\n", buf.items);
} }
test "RepeatableCommand formatConfig multiple items" { test "RepeatableCommand formatConfig multiple items" {
@ -7122,7 +7140,40 @@ pub const RepeatableCommand = struct {
try list.parseCLI(alloc, "title:Bobr, action:text:kurwa"); try list.parseCLI(alloc, "title:Bobr, action:text:kurwa");
try list.parseCLI(alloc, "title:Ja, description: pierdole, action:text:jakie bydle"); try list.parseCLI(alloc, "title:Ja, description: pierdole, action:text:jakie bydle");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
try std.testing.expectEqualSlices(u8, "a = title:Bobr,action:text:kurwa\na = title:Ja,description:pierdole,action:text:jakie bydle\n", buf.items); try std.testing.expectEqualSlices(u8, "a = title:\"Bobr\",action:\"text:kurwa\"\na = title:\"Ja\",description:\"pierdole\",action:\"text:jakie bydle\"\n", buf.items);
}
test "RepeatableCommand parseCLI commas" {
const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator);
defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
{
var list: RepeatableCommand = .{};
try list.parseCLI(alloc, "title:\"Bo,br\",action:\"text:kur,wa\"");
try testing.expectEqual(@as(usize, 1), list.value.items.len);
const item = list.value.items[0];
try testing.expectEqualStrings("Bo,br", item.title);
try testing.expectEqualStrings("", item.description);
try testing.expect(item.action == .text);
try testing.expectEqualStrings("kur,wa", item.action.text);
}
{
var list: RepeatableCommand = .{};
try list.parseCLI(alloc, "title:\"Bo,br\",description:\"abc,def\",action:text:kurwa");
try testing.expectEqual(@as(usize, 1), list.value.items.len);
const item = list.value.items[0];
try testing.expectEqualStrings("Bo,br", item.title);
try testing.expectEqualStrings("abc,def", item.description);
try testing.expect(item.action == .text);
try testing.expectEqualStrings("kurwa", item.action.text);
}
} }
}; };

View File

@ -1213,7 +1213,7 @@ pub const Action = union(enum) {
const value_info = @typeInfo(Value); const value_info = @typeInfo(Value);
switch (Value) { switch (Value) {
void => {}, void => {},
[]const u8 => try writer.print("{s}", .{value}), []const u8 => try std.zig.stringEscape(value, "", .{}, writer),
else => switch (value_info) { else => switch (value_info) {
.@"enum" => try writer.print("{s}", .{@tagName(value)}), .@"enum" => try writer.print("{s}", .{@tagName(value)}),
.float => try writer.print("{d}", .{value}), .float => try writer.print("{d}", .{value}),
@ -3227,3 +3227,18 @@ test "parse: set_font_size" {
try testing.expectEqual(13.5, binding.action.set_font_size); try testing.expectEqual(13.5, binding.action.set_font_size);
} }
} }
test "action: format" {
const testing = std.testing;
const alloc = testing.allocator;
const a: Action = .{ .text = "👻" };
var buf: std.ArrayListUnmanaged(u8) = .empty;
defer buf.deinit(alloc);
const writer = buf.writer(alloc);
try a.format("", .{}, writer);
try testing.expectEqualStrings("text:\\xf0\\x9f\\x91\\xbb", buf.items);
}

View File

@ -472,13 +472,18 @@ fn actionCommands(action: Action.Key) []const Command {
.description = "Quit the application.", .description = "Quit the application.",
}}, }},
.text => comptime &.{.{
.action = .{ .text = "👻" },
.title = "Ghostty",
.description = "Put a little Ghostty in your terminal.",
}},
// No commands because they're parameterized and there // No commands because they're parameterized and there
// aren't obvious values users would use. It is possible that // aren't obvious values users would use. It is possible that
// these may have commands in the future if there are very // these may have commands in the future if there are very
// common values that users tend to use. // common values that users tend to use.
.csi, .csi,
.esc, .esc,
.text,
.cursor_key, .cursor_key,
.set_font_size, .set_font_size,
.scroll_page_fractional, .scroll_page_fractional,

255
src/lib/allocator.zig Normal file
View File

@ -0,0 +1,255 @@
const std = @import("std");
const builtin = @import("builtin");
const testing = std.testing;
/// Useful alias since they're required to create Zig allocators
pub const ZigVTable = std.mem.Allocator.VTable;
/// The VTable required by the C interface.
/// C: GhosttyAllocatorVtable
pub const VTable = extern struct {
alloc: *const fn (*anyopaque, len: usize, alignment: u8, ret_addr: usize) callconv(.c) ?[*]u8,
resize: *const fn (*anyopaque, memory: [*]u8, memory_len: usize, alignment: u8, new_len: usize, ret_addr: usize) callconv(.c) bool,
remap: *const fn (*anyopaque, memory: [*]u8, memory_len: usize, alignment: u8, new_len: usize, ret_addr: usize) callconv(.c) ?[*]u8,
free: *const fn (*anyopaque, memory: [*]u8, memory_len: usize, alignment: u8, ret_addr: usize) callconv(.c) void,
};
/// Returns an allocator to use for the given possibly-null C allocator,
/// ensuring some allocator is always returned.
pub fn default(c_alloc_: ?*const Allocator) std.mem.Allocator {
// If we're given an allocator, use it.
if (c_alloc_) |c_alloc| return c_alloc.zig();
// If we have libc, use that. We prefer libc if we have it because
// its generally fast but also lets the embedder easily override
// malloc/free with custom allocators like mimalloc or something.
if (comptime builtin.link_libc) return std.heap.c_allocator;
// No libc, use the preferred allocator for releases which is the
// Zig SMP allocator.
return std.heap.smp_allocator;
}
/// The Allocator interface for custom memory allocation strategies
/// within C libghostty APIs.
///
/// This -- purposely -- matches the Zig allocator interface. We do this
/// for two reasons: (1) Zig's allocator interface is well proven in
/// the real world to be flexible and useful, and (2) it allows us to
/// easily convert C allocators to Zig allocators and vice versa, since
/// we're written in Zig.
///
/// C: GhosttyAllocator
pub const Allocator = extern struct {
ctx: *anyopaque,
vtable: *const VTable,
/// vtable for the Zig allocator interface to map our extern
/// allocator to Zig's allocator interface.
pub const zig_vtable: ZigVTable = .{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
};
/// Create a C allocator from a Zig allocator. This requires that
/// the Zig allocator be pointer-stable for the lifetime of the
/// C allocator.
pub fn fromZig(zig_alloc: *const std.mem.Allocator) Allocator {
return .{
.ctx = @ptrCast(@constCast(zig_alloc)),
.vtable = &ZigAllocator.vtable,
};
}
/// Create a Zig allocator from this C allocator. This requires
/// a pointer to a Zig allocator vtable that we can populate with
/// our callbacks.
pub fn zig(self: *const Allocator) std.mem.Allocator {
return .{
.ptr = @ptrCast(@constCast(self)),
.vtable = &zig_vtable,
};
}
fn alloc(
ctx: *anyopaque,
len: usize,
alignment: std.mem.Alignment,
ra: usize,
) ?[*]u8 {
const self: *Allocator = @ptrCast(@alignCast(ctx));
return self.vtable.alloc(
self.ctx,
len,
@intFromEnum(alignment),
ra,
);
}
fn resize(
ctx: *anyopaque,
old_mem: []u8,
alignment: std.mem.Alignment,
new_len: usize,
ra: usize,
) bool {
const self: *Allocator = @ptrCast(@alignCast(ctx));
return self.vtable.resize(
self.ctx,
old_mem.ptr,
old_mem.len,
@intFromEnum(alignment),
new_len,
ra,
);
}
fn remap(
ctx: *anyopaque,
old_mem: []u8,
alignment: std.mem.Alignment,
new_len: usize,
ra: usize,
) ?[*]u8 {
const self: *Allocator = @ptrCast(@alignCast(ctx));
return self.vtable.remap(
self.ctx,
old_mem.ptr,
old_mem.len,
@intFromEnum(alignment),
new_len,
ra,
);
}
fn free(
ctx: *anyopaque,
old_mem: []u8,
alignment: std.mem.Alignment,
ra: usize,
) void {
const self: *Allocator = @ptrCast(@alignCast(ctx));
self.vtable.free(
self.ctx,
old_mem.ptr,
old_mem.len,
@intFromEnum(alignment),
ra,
);
}
};
/// An allocator implementation that wraps a Zig allocator so that
/// it can be exposed to C.
const ZigAllocator = struct {
const vtable: VTable = .{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
};
fn alloc(
ctx: *anyopaque,
len: usize,
alignment: u8,
ra: usize,
) callconv(.c) ?[*]u8 {
const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
return zig_alloc.vtable.alloc(
zig_alloc.ptr,
len,
@enumFromInt(alignment),
ra,
);
}
fn resize(
ctx: *anyopaque,
memory: [*]u8,
memory_len: usize,
alignment: u8,
new_len: usize,
ra: usize,
) callconv(.c) bool {
const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
return zig_alloc.vtable.resize(
zig_alloc.ptr,
memory[0..memory_len],
@enumFromInt(alignment),
new_len,
ra,
);
}
fn remap(
ctx: *anyopaque,
memory: [*]u8,
memory_len: usize,
alignment: u8,
new_len: usize,
ra: usize,
) callconv(.c) ?[*]u8 {
const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
return zig_alloc.vtable.remap(
zig_alloc.ptr,
memory[0..memory_len],
@enumFromInt(alignment),
new_len,
ra,
);
}
fn free(
ctx: *anyopaque,
memory: [*]u8,
memory_len: usize,
alignment: u8,
ra: usize,
) callconv(.c) void {
const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
return zig_alloc.vtable.free(
zig_alloc.ptr,
memory[0..memory_len],
@enumFromInt(alignment),
ra,
);
}
};
/// libc Allocator, requires linking libc
pub const c_allocator: Allocator = .fromZig(&std.heap.c_allocator);
/// Allocator that can be sent to the C API that does full
/// leak checking within Zig tests. This should only be used from
/// Zig tests.
pub const test_allocator: Allocator = b: {
if (!builtin.is_test) @compileError("test_allocator can only be used in tests");
break :b .fromZig(&testing.allocator);
};
test "c allocator" {
if (!comptime builtin.link_libc) return error.SkipZigTest;
const alloc = c_allocator.zig();
const str = try alloc.alloc(u8, 10);
defer alloc.free(str);
try testing.expectEqual(10, str.len);
}
test "fba allocator" {
var buf: [1024]u8 = undefined;
var fba: std.heap.FixedBufferAllocator = .init(&buf);
const zig_alloc = fba.allocator();
// Convert the Zig allocator to a C interface
const c_alloc: Allocator = .fromZig(&zig_alloc);
// Convert back to Zig so we can test it.
const alloc = c_alloc.zig();
const str = try alloc.alloc(u8, 10);
defer alloc.free(str);
try testing.expectEqual(10, str.len);
}

View File

@ -65,6 +65,19 @@ pub const EraseLine = terminal.EraseLine;
pub const TabClear = terminal.TabClear; pub const TabClear = terminal.TabClear;
pub const Attribute = terminal.Attribute; pub const Attribute = terminal.Attribute;
comptime {
// If we're building the C library (vs. the Zig module) then
// we want to reference the C API so that it gets exported.
if (terminal.is_c_lib) {
const c = terminal.c_api;
@export(&c.osc_new, .{ .name = "ghostty_osc_new" });
@export(&c.osc_free, .{ .name = "ghostty_osc_free" });
}
}
test { test {
_ = terminal; _ = terminal;
// Tests always test the C API
_ = terminal.c_api;
} }

View File

@ -51,4 +51,5 @@ pub const locales = [_][:0]const u8{
"hu_HU.UTF-8", "hu_HU.UTF-8",
"he_IL.UTF-8", "he_IL.UTF-8",
"zh_TW.UTF-8", "zh_TW.UTF-8",
"hr_HR.UTF-8",
}; };

49
src/terminal/c_api.zig Normal file
View File

@ -0,0 +1,49 @@
const std = @import("std");
const assert = std.debug.assert;
const builtin = @import("builtin");
const lib_alloc = @import("../lib/allocator.zig");
const CAllocator = lib_alloc.Allocator;
const osc = @import("osc.zig");
/// C: GhosttyOscParser
pub const OscParser = ?*osc.Parser;
/// C: GhosttyResult
pub const Result = enum(c_int) {
success = 0,
out_of_memory = -1,
};
pub fn osc_new(
alloc_: ?*const CAllocator,
result: *OscParser,
) callconv(.c) Result {
const alloc = lib_alloc.default(alloc_);
const ptr = alloc.create(osc.Parser) catch
return .out_of_memory;
ptr.* = .initAlloc(alloc);
result.* = ptr;
return .success;
}
pub fn osc_free(parser_: OscParser) callconv(.c) void {
// C-built parsers always have an associated allocator.
const parser = parser_ orelse return;
const alloc = parser.alloc.?;
parser.deinit();
alloc.destroy(parser);
}
test {
_ = lib_alloc;
}
test "osc" {
const testing = std.testing;
var p: OscParser = undefined;
try testing.expectEqual(Result.success, osc_new(
&lib_alloc.test_allocator,
&p,
));
osc_free(p);
}

View File

@ -62,6 +62,10 @@ pub const Attribute = sgr.Attribute;
pub const isSafePaste = sanitize.isSafePaste; pub const isSafePaste = sanitize.isSafePaste;
/// This is set to true when we're building the C library.
pub const is_c_lib = @import("root") == @import("../lib_vt.zig");
pub const c_api = @import("c_api.zig");
test { test {
@import("std").testing.refAllDecls(@This()); @import("std").testing.refAllDecls(@This());