Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Merijntje Tak
2025-07-12 08:52:42 +02:00
121 changed files with 3049 additions and 1503 deletions

View File

@ -1,6 +1,6 @@
root = true root = true
[*.{sh,bash}] [*.{sh,bash,elv}]
indent_size = 2 indent_size = 2
indent_style = space indent_style = space

View File

@ -36,13 +36,13 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- name: Setup Nix - name: Setup Nix
uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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

@ -57,7 +57,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
# 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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -211,7 +211,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
# 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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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

@ -83,13 +83,13 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.0
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
@ -130,7 +130,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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

@ -107,12 +107,12 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -164,7 +164,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
# 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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -175,6 +175,9 @@ jobs:
- name: XCode Select - name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_26.0.app run: sudo xcode-select -s /Applications/Xcode_26.0.app
- name: Xcode Version
run: xcodebuild -version
# Setup Sparkle # Setup Sparkle
- name: Setup Sparkle - name: Setup Sparkle
env: env:
@ -381,7 +384,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
# 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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -392,6 +395,9 @@ jobs:
- name: XCode Select - name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_26.0.app run: sudo xcode-select -s /Applications/Xcode_26.0.app
- name: Xcode Version
run: xcodebuild -version
# Setup Sparkle # Setup Sparkle
- name: Setup Sparkle - name: Setup Sparkle
env: env:
@ -558,7 +564,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
# 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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -569,6 +575,9 @@ jobs:
- name: XCode Select - name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_26.0.app run: sudo xcode-select -s /Applications/Xcode_26.0.app
- name: Xcode Version
run: xcodebuild -version
# Setup Sparkle # Setup Sparkle
- name: Setup Sparkle - name: Setup Sparkle
env: env:

View File

@ -31,6 +31,7 @@ jobs:
- prettier - prettier
- alejandra - alejandra
- typos - typos
- shellcheck
- translations - translations
- blueprint-compiler - blueprint-compiler
- test-pkg-linux - test-pkg-linux
@ -68,14 +69,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -99,14 +100,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -135,14 +136,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -164,14 +165,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -197,14 +198,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -241,14 +242,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -277,7 +278,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# 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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -288,6 +289,9 @@ jobs:
- name: Xcode Select - name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_26.0.app run: sudo xcode-select -s /Applications/Xcode_26.0.app
- name: Xcode Version
run: xcodebuild -version
- name: get the Zig deps - name: get the Zig deps
id: deps id: deps
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
@ -357,7 +361,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# 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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -409,7 +413,7 @@ jobs:
mkdir dist mkdir dist
tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
@ -504,14 +508,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -546,14 +550,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -594,14 +598,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -621,7 +625,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# 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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -649,12 +653,12 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -677,12 +681,12 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -704,12 +708,12 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -731,12 +735,12 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -758,12 +762,12 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -775,6 +779,40 @@ jobs:
- name: typos check - name: typos check
run: nix develop -c typos run: nix develop -c typos
shellcheck:
if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-xsm
timeout-minutes: 60
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with:
path: |
/nix
/zig
- uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.0
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
skipPush: true
useDaemon: false # sometimes fails on short jobs
- name: shellcheck
run: |
nix develop -c shellcheck \
--check-sourced \
--color=always \
--severity=warning \
--shell=bash \
--external-sources \
$(find . \( -name "*.sh" -o -name "*.bash" \) -type f ! -path "./zig-out/*" ! -path "./macos/build/*" ! -path "./.git/*" | sort)
translations: translations:
if: github.repository == 'ghostty-org/ghostty' if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-xsm runs-on: namespace-profile-ghostty-xsm
@ -785,12 +823,12 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -812,12 +850,12 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -847,14 +885,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/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@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 - uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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
@ -905,13 +943,13 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- name: Setup Nix - name: Setup Nix
uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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

@ -22,14 +22,14 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Setup Cache - name: Setup Cache
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 uses: namespacelabs/nscloud-cache-action@0ac1550c04676e19d39872be6216ccbf9c6bab43 # v1.2.9
with: with:
path: | path: |
/nix /nix
/zig /zig
- name: Setup Nix - name: Setup Nix
uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 uses: cachix/install-nix-action@cebd211ec2008b83bda8fb0b21c3c072f004fe04 # v31.5.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

@ -8,7 +8,22 @@ comptime {
} }
pub fn build(b: *std.Build) !void { pub fn build(b: *std.Build) !void {
// This defines all the available build options (e.g. `-D`).
const config = try buildpkg.Config.init(b); const config = try buildpkg.Config.init(b);
const test_filter = b.option(
[]const u8,
"test-filter",
"Filter for test. Only applies to Zig tests.",
);
// All our steps which we'll hook up later. The steps are shown
// up here just so that they are more self-documenting.
const run_step = b.step("run", "Run the app");
const test_step = b.step("test", "Run all tests");
const translations_step = b.step(
"update-translations",
"Update translation files",
);
// Ghostty resources like terminfo, shell integration, themes, etc. // Ghostty resources like terminfo, shell integration, themes, etc.
const resources = try buildpkg.GhosttyResources.init(b, &config); const resources = try buildpkg.GhosttyResources.init(b, &config);
@ -131,7 +146,6 @@ pub fn build(b: *std.Build) !void {
b.getInstallPath(.prefix, "share/ghostty"), b.getInstallPath(.prefix, "share/ghostty"),
); );
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step); run_step.dependOn(&run_cmd.step);
break :run; break :run;
} }
@ -157,16 +171,18 @@ pub fn build(b: *std.Build) !void {
}, },
); );
const run_step = b.step("run", "Run the app"); // Run uses the native macOS app
run_step.dependOn(&macos_app_native_only.open.step); run_step.dependOn(&macos_app_native_only.open.step);
// If we have no test filters, install the tests too
if (test_filter == null) {
macos_app_native_only.addTestStepDependencies(test_step);
}
} }
} }
// Tests // Tests
{ {
const test_step = b.step("test", "Run all tests");
const test_filter = b.option([]const u8, "test-filter", "Filter for test");
const test_exe = b.addTest(.{ const test_exe = b.addTest(.{
.name = "ghostty-test", .name = "ghostty-test",
.filters = if (test_filter) |v| &.{v} else &.{}, .filters = if (test_filter) |v| &.{v} else &.{},
@ -180,18 +196,13 @@ pub fn build(b: *std.Build) !void {
}), }),
}); });
{ if (config.emit_test_exe) b.installArtifact(test_exe);
if (config.emit_test_exe) b.installArtifact(test_exe); _ = try deps.add(test_exe);
_ = try deps.add(test_exe); const test_run = b.addRunArtifact(test_exe);
const test_run = b.addRunArtifact(test_exe); test_step.dependOn(&test_run.step);
test_step.dependOn(&test_run.step);
}
} }
// update-translations does what it sounds like and updates the "pot" // update-translations does what it sounds like and updates the "pot"
// files. These should be committed to the repo. // files. These should be committed to the repo.
{ translations_step.dependOn(i18n.update_step);
const step = b.step("update-translations", "Update translation files");
step.dependOn(i18n.update_step);
}
} }

View File

@ -680,6 +680,12 @@ typedef struct {
uintptr_t len; uintptr_t len;
} ghostty_action_open_url_s; } ghostty_action_open_url_s;
// apprt.surface.Message.ChildExited
typedef struct {
uint32_t exit_code;
uint64_t timetime_ms;
} ghostty_surface_message_childexited_s;
// apprt.Action.Key // apprt.Action.Key
typedef enum { typedef enum {
GHOSTTY_ACTION_QUIT, GHOSTTY_ACTION_QUIT,
@ -731,6 +737,7 @@ typedef enum {
GHOSTTY_ACTION_REDO, GHOSTTY_ACTION_REDO,
GHOSTTY_ACTION_CHECK_FOR_UPDATES, GHOSTTY_ACTION_CHECK_FOR_UPDATES,
GHOSTTY_ACTION_OPEN_URL, GHOSTTY_ACTION_OPEN_URL,
GHOSTTY_ACTION_SHOW_CHILD_EXITED
} ghostty_action_tag_e; } ghostty_action_tag_e;
typedef union { typedef union {
@ -759,6 +766,7 @@ typedef union {
ghostty_action_reload_config_s reload_config; ghostty_action_reload_config_s reload_config;
ghostty_action_config_change_s config_change; ghostty_action_config_change_s config_change;
ghostty_action_open_url_s open_url; ghostty_action_open_url_s open_url;
ghostty_surface_message_childexited_s child_exited;
} ghostty_action_u; } ghostty_action_u;
typedef struct { typedef struct {
@ -932,6 +940,9 @@ bool ghostty_inspector_metal_shutdown(ghostty_inspector_t);
// Don't use these unless you know what you're doing. // Don't use these unless you know what you're doing.
void ghostty_set_window_background_blur(ghostty_app_t, void*); void ghostty_set_window_background_blur(ghostty_app_t, void*);
// Benchmark API, if available.
bool ghostty_benchmark_cli(const char*, const char*);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 56; objectVersion = 70;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -152,6 +152,16 @@
FC9ABA9C2D0F53F80020D4C8 /* bash-completion in Resources */ = {isa = PBXBuildFile; fileRef = FC9ABA9B2D0F538D0020D4C8 /* bash-completion */; }; FC9ABA9C2D0F53F80020D4C8 /* bash-completion in Resources */ = {isa = PBXBuildFile; fileRef = FC9ABA9B2D0F538D0020D4C8 /* bash-completion */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
A54F45F72E1F047A0046BD5C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A5B30529299BEAAA0047F10C /* Project object */;
proxyType = 1;
remoteGlobalIDString = A5B30530299BEAAA0047F10C;
remoteInfo = Ghostty;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
29C15B1C2CDC3B2000520DD4 /* bat */ = {isa = PBXFileReference; lastKnownFileType = folder; name = bat; path = "../zig-out/share/bat"; sourceTree = "<group>"; }; 29C15B1C2CDC3B2000520DD4 /* bat */ = {isa = PBXFileReference; lastKnownFileType = folder; name = bat; path = "../zig-out/share/bat"; sourceTree = "<group>"; };
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = "<group>"; }; 3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = "<group>"; };
@ -199,6 +209,7 @@
A54B0CEC2D0CFB7300CBEFF8 /* ColorizedGhosttyIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorizedGhosttyIcon.swift; sourceTree = "<group>"; }; A54B0CEC2D0CFB7300CBEFF8 /* ColorizedGhosttyIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorizedGhosttyIcon.swift; sourceTree = "<group>"; };
A54B0CEE2D0D2E2400CBEFF8 /* ColorizedGhosttyIconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorizedGhosttyIconImage.swift; sourceTree = "<group>"; }; A54B0CEE2D0D2E2400CBEFF8 /* ColorizedGhosttyIconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorizedGhosttyIconImage.swift; sourceTree = "<group>"; };
A54D786B2CA79788001B19B1 /* BaseTerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTerminalController.swift; sourceTree = "<group>"; }; A54D786B2CA79788001B19B1 /* BaseTerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTerminalController.swift; sourceTree = "<group>"; };
A54F45F32E1F047A0046BD5C /* GhosttyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GhosttyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
A553F4122E06EB1600257779 /* Ghostty.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; name = Ghostty.icon; path = ../images/Ghostty.icon; sourceTree = SOURCE_ROOT; }; A553F4122E06EB1600257779 /* Ghostty.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; name = Ghostty.icon; path = ../images/Ghostty.icon; sourceTree = SOURCE_ROOT; };
A5593FDE2DF8D57100B47B10 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = "<group>"; }; A5593FDE2DF8D57100B47B10 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = "<group>"; };
A5593FE02DF8D73400B47B10 /* HiddenTitlebarTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenTitlebarTerminalWindow.swift; sourceTree = "<group>"; }; A5593FE02DF8D73400B47B10 /* HiddenTitlebarTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenTitlebarTerminalWindow.swift; sourceTree = "<group>"; };
@ -291,7 +302,18 @@
FC9ABA9B2D0F538D0020D4C8 /* bash-completion */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "bash-completion"; path = "../zig-out/share/bash-completion"; sourceTree = "<group>"; }; FC9ABA9B2D0F538D0020D4C8 /* bash-completion */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "bash-completion"; path = "../zig-out/share/bash-completion"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
A54F45F42E1F047A0046BD5C /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
A54F45F02E1F047A0046BD5C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
A5B3052E299BEAAA0047F10C /* Frameworks */ = { A5B3052E299BEAAA0047F10C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -590,6 +612,7 @@
A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */, A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */,
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */, 3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */,
A54CD6ED299BEB14008C95BB /* Sources */, A54CD6ED299BEB14008C95BB /* Sources */,
A54F45F42E1F047A0046BD5C /* Tests */,
A5D495A3299BECBA00DD1313 /* Frameworks */, A5D495A3299BECBA00DD1313 /* Frameworks */,
A5A1F8862A489D7400D1E8BC /* Resources */, A5A1F8862A489D7400D1E8BC /* Resources */,
A5B30532299BEAAA0047F10C /* Products */, A5B30532299BEAAA0047F10C /* Products */,
@ -601,6 +624,7 @@
children = ( children = (
A5B30531299BEAAA0047F10C /* Ghostty.app */, A5B30531299BEAAA0047F10C /* Ghostty.app */,
A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */, A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */,
A54F45F32E1F047A0046BD5C /* GhosttyTests.xctest */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -674,6 +698,29 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
A54F45F22E1F047A0046BD5C /* GhosttyTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = A54F45FC2E1F047A0046BD5C /* Build configuration list for PBXNativeTarget "GhosttyTests" */;
buildPhases = (
A54F45EF2E1F047A0046BD5C /* Sources */,
A54F45F02E1F047A0046BD5C /* Frameworks */,
A54F45F12E1F047A0046BD5C /* Resources */,
);
buildRules = (
);
dependencies = (
A54F45F82E1F047A0046BD5C /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
A54F45F42E1F047A0046BD5C /* Tests */,
);
name = GhosttyTests;
packageProductDependencies = (
);
productName = GhosttyTests;
productReference = A54F45F32E1F047A0046BD5C /* GhosttyTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
A5B30530299BEAAA0047F10C /* Ghostty */ = { A5B30530299BEAAA0047F10C /* Ghostty */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = A5B30540299BEAAB0047F10C /* Build configuration list for PBXNativeTarget "Ghostty" */; buildConfigurationList = A5B30540299BEAAB0047F10C /* Build configuration list for PBXNativeTarget "Ghostty" */;
@ -718,9 +765,13 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1520; LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 1610; LastUpgradeCheck = 1610;
TargetAttributes = { TargetAttributes = {
A54F45F22E1F047A0046BD5C = {
CreatedOnToolsVersion = 26.0;
TestTargetID = A5B30530299BEAAA0047F10C;
};
A5B30530299BEAAA0047F10C = { A5B30530299BEAAA0047F10C = {
CreatedOnToolsVersion = 14.2; CreatedOnToolsVersion = 14.2;
LastSwiftMigration = 1510; LastSwiftMigration = 1510;
@ -748,11 +799,19 @@
targets = ( targets = (
A5B30530299BEAAA0047F10C /* Ghostty */, A5B30530299BEAAA0047F10C /* Ghostty */,
A5D4499C2B53AE7B000F5B83 /* Ghostty-iOS */, A5D4499C2B53AE7B000F5B83 /* Ghostty-iOS */,
A54F45F22E1F047A0046BD5C /* GhosttyTests */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
A54F45F12E1F047A0046BD5C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
A5B3052F299BEAAA0047F10C /* Resources */ = { A5B3052F299BEAAA0047F10C /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -794,6 +853,13 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
A54F45EF2E1F047A0046BD5C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
A5B3052D299BEAAA0047F10C /* Sources */ = { A5B3052D299BEAAA0047F10C /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -925,6 +991,14 @@
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
A54F45F82E1F047A0046BD5C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = A5B30530299BEAAA0047F10C /* Ghostty */;
targetProxy = A54F45F72E1F047A0046BD5C /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
3B39CAA22B33946300DABEB8 /* ReleaseLocal */ = { 3B39CAA22B33946300DABEB8 /* ReleaseLocal */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
@ -1034,6 +1108,76 @@
}; };
name = ReleaseLocal; name = ReleaseLocal;
}; };
A54F45F92E1F047A0046BD5C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.GhosttyTests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Ghostty.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ghostty";
};
name = Debug;
};
A54F45FA2E1F047A0046BD5C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.GhosttyTests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Ghostty.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ghostty";
};
name = Release;
};
A54F45FB2E1F047A0046BD5C /* ReleaseLocal */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.GhosttyTests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Ghostty.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ghostty";
};
name = ReleaseLocal;
};
A5B3053E299BEAAB0047F10C /* Debug */ = { A5B3053E299BEAAB0047F10C /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@ -1378,6 +1522,16 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
A54F45FC2E1F047A0046BD5C /* Build configuration list for PBXNativeTarget "GhosttyTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
A54F45F92E1F047A0046BD5C /* Debug */,
A54F45FA2E1F047A0046BD5C /* Release */,
A54F45FB2E1F047A0046BD5C /* ReleaseLocal */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = ReleaseLocal;
};
A5B3052C299BEAAA0047F10C /* Build configuration list for PBXProject "Ghostty" */ = { A5B3052C299BEAAA0047F10C /* Build configuration list for PBXProject "Ghostty" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (

View File

@ -28,6 +28,19 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES"> shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A54F45F22E1F047A0046BD5C"
BuildableName = "GhosttyTests.xctest"
BlueprintName = "GhosttyTests"
ReferencedContainer = "container:Ghostty.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"

View File

@ -579,6 +579,8 @@ extension Ghostty {
case GHOSTTY_ACTION_SIZE_LIMIT: case GHOSTTY_ACTION_SIZE_LIMIT:
fallthrough fallthrough
case GHOSTTY_ACTION_QUIT_TIMER: case GHOSTTY_ACTION_QUIT_TIMER:
fallthrough
case GHOSTTY_ACTION_SHOW_CHILD_EXITED:
Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)") Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)")
return false return false
default: default:

View File

@ -0,0 +1,32 @@
//
// GhosttyTests.swift
// GhosttyTests
//
// Created by Mitchell Hashimoto on 7/9/25.
//
import Testing
import GhosttyKit
extension Tag {
@Tag static var benchmark: Self
}
/// The whole idea behind these benchmarks is that they're run by right-clicking
/// in Xcode and using "Profile" to open them in instruments. They aren't meant to
/// be run in general.
///
/// When running them, set the `if:` to `true`. There's probably a better
/// programmatic way to do this but I don't know it yet!
@Suite(
"Benchmarks",
.enabled(if: false),
.tags(.benchmark)
)
struct BenchmarkTests {
@Test func example() async throws {
ghostty_benchmark_cli(
"terminal-stream",
"--data=/Users/mitchellh/Documents/ghostty/bug.osc.txt")
}
}

View File

@ -6,7 +6,7 @@
set -e # Exit immediately if a command exits with a non-zero status set -e # Exit immediately if a command exits with a non-zero status
SCRIPT_PATH="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" SCRIPT_PATH="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)"
INPUT_FILE="$SCRIPT_PATH/../../build.zig.zon2json-lock" INPUT_FILE="$SCRIPT_PATH/../../build.zig.zon2json-lock"
OUTPUT_DIR="blob" OUTPUT_DIR="blob"

View File

@ -61,6 +61,7 @@
pinact, pinact,
hyperfine, hyperfine,
typos, typos,
shellcheck,
uv, uv,
wayland, wayland,
wayland-scanner, wayland-scanner,
@ -101,6 +102,7 @@ in
alejandra alejandra
pinact pinact
typos typos
shellcheck
# Testing # Testing
parallel parallel

View File

@ -15,15 +15,23 @@ pub fn build(b: *std.Build) !void {
}); });
const macos = b.dependency("macos", .{ .target = target, .optimize = optimize }); const macos = b.dependency("macos", .{ .target = target, .optimize = optimize });
const module = b.addModule("harfbuzz", .{ const module = harfbuzz: {
.root_source_file = b.path("main.zig"), const module = b.addModule("harfbuzz", .{
.target = target, .root_source_file = b.path("main.zig"),
.optimize = optimize, .target = target,
.imports = &.{ .optimize = optimize,
.{ .name = "freetype", .module = freetype.module("freetype") }, .imports = &.{
.{ .name = "macos", .module = macos.module("macos") }, .{ .name = "freetype", .module = freetype.module("freetype") },
}, .{ .name = "macos", .module = macos.module("macos") },
}); },
});
const options = b.addOptions();
options.addOption(bool, "coretext", coretext_enabled);
options.addOption(bool, "freetype", freetype_enabled);
module.addOptions("build_options", options);
break :harfbuzz module;
};
// For dynamic linking, we prefer dynamic linking and to search by // For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library // mode first. Mode first will search all paths for a dynamic library

View File

@ -1,7 +1,8 @@
const builtin = @import("builtin"); const builtin = @import("builtin");
const build_options = @import("build_options");
pub const c = @cImport({ pub const c = @cImport({
@cInclude("hb.h"); @cInclude("hb.h");
@cInclude("hb-ft.h"); if (build_options.freetype) @cInclude("hb-ft.h");
if (builtin.os.tag == .macos) @cInclude("hb-coretext.h"); if (build_options.coretext) @cInclude("hb-coretext.h");
}); });

View File

@ -18,15 +18,12 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize, .optimize = optimize,
}); });
var flags = std.ArrayList([]const u8).init(b.allocator);
defer flags.deinit();
lib.addCSourceFile(.{ lib.addCSourceFile(.{
.file = b.path("os/zig_log.c"), .file = b.path("os/zig_macos.c"),
.flags = flags.items, .flags = &.{"-std=c99"},
}); });
lib.addCSourceFile(.{ lib.addCSourceFile(.{
.file = b.path("text/ext.c"), .file = b.path("text/ext.c"),
.flags = flags.items,
}); });
lib.linkFramework("CoreFoundation"); lib.linkFramework("CoreFoundation");
lib.linkFramework("CoreGraphics"); lib.linkFramework("CoreGraphics");

View File

@ -23,6 +23,7 @@ pub const c = @cImport({
@cInclude("IOSurface/IOSurfaceRef.h"); @cInclude("IOSurface/IOSurfaceRef.h");
@cInclude("dispatch/dispatch.h"); @cInclude("dispatch/dispatch.h");
@cInclude("os/log.h"); @cInclude("os/log.h");
@cInclude("os/signpost.h");
if (builtin.os.tag == .macos) { if (builtin.os.tag == .macos) {
@cInclude("Carbon/Carbon.h"); @cInclude("Carbon/Carbon.h");

View File

@ -1,6 +1,7 @@
const log = @import("os/log.zig"); const log = @import("os/log.zig");
pub const c = @import("os/c.zig"); pub const c = @import("os/c.zig");
pub const signpost = @import("os/signpost.zig");
pub const Log = log.Log; pub const Log = log.Log;
pub const LogType = log.LogType; pub const LogType = log.LogType;

View File

@ -8,10 +8,10 @@ pub const Log = opaque {
subsystem: [:0]const u8, subsystem: [:0]const u8,
category: [:0]const u8, category: [:0]const u8,
) *Log { ) *Log {
return @as(?*Log, @ptrFromInt(@intFromPtr(c.os_log_create( return @ptrCast(c.os_log_create(
subsystem.ptr, subsystem.ptr,
category.ptr, category.ptr,
)))).?; ).?);
} }
pub fn release(self: *Log) void { pub fn release(self: *Log) void {
@ -32,7 +32,11 @@ pub const Log = opaque {
comptime format: []const u8, comptime format: []const u8,
args: anytype, args: anytype,
) void { ) void {
const str = nosuspend std.fmt.allocPrintZ(alloc, format, args) catch return; const str = nosuspend std.fmt.allocPrintZ(
alloc,
format,
args,
) catch return;
defer alloc.free(str); defer alloc.free(str);
zig_os_log_with_type(self, typ, str.ptr); zig_os_log_with_type(self, typ, str.ptr);
} }

214
pkg/macos/os/signpost.zig Normal file
View File

@ -0,0 +1,214 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
const logpkg = @import("log.zig");
const Log = logpkg.Log;
/// This should be called once at the start of the program to intialize
/// some required state for signpost logging.
///
/// This is all to workaround a Zig bug:
/// https://github.com/ziglang/zig/issues/24370
pub fn init() void {
if (__dso_handle != null) return;
const sym = comptime sym: {
const root = @import("root");
// If we have a main function, use that as the symbol.
if (@hasDecl(root, "main")) break :sym root.main;
// Otherwise, we're in a library, so we just use the first
// function in our root module. I actually don't know if this is
// all required or if we can just use the real `__dso_handle` symbol,
// but this seems to work for now.
for (@typeInfo(root).@"struct".decls) |decl_info| {
const decl = @field(root, decl_info.name);
if (@typeInfo(@TypeOf(decl)) == .@"fn") break :sym decl;
}
@compileError("no functions found in root module");
};
// Since __dso_handle is not automatically populated by the linker,
// we populate it by looking up the main function's module address
// which should be a mach-o header.
var info: DlInfo = undefined;
const result = dladdr(sym, &info);
assert(result != 0);
__dso_handle = @ptrCast(@alignCast(info.dli_fbase));
}
/// This should REALLY be an extern var that is populated by the linker,
/// but there is a Zig bug: https://github.com/ziglang/zig/issues/24370
var __dso_handle: ?*c.mach_header = null;
// Import the necessary C functions and types
extern "c" fn dladdr(addr: ?*const anyopaque, info: *DlInfo) c_int;
// Define the Dl_info structure
const DlInfo = extern struct {
dli_fname: [*:0]const u8, // Pathname of shared object
dli_fbase: ?*anyopaque, // Base address of shared object
dli_sname: [*:0]const u8, // Name of nearest symbol
dli_saddr: ?*anyopaque, // Address of nearest symbol
};
/// Checks whether signpost logging is enabled for the given log handle.
/// Returns true if signposts will be recorded for this log, false otherwise.
/// This can be used to avoid expensive operations when signpost logging is disabled.
///
/// https://developer.apple.com/documentation/os/os_signpost_enabled?language=objc
pub fn enabled(log: *Log) bool {
return c.os_signpost_enabled(@ptrCast(log));
}
/// Emits a signpost event - a single point in time marker.
/// Events are useful for marking when specific actions occur, such as
/// user interactions, state changes, or other discrete occurrences.
/// The event will appear as a vertical line in Instruments.
///
/// https://developer.apple.com/documentation/os/os_signpost_event_emit?language=objc
pub fn emitEvent(
log: *Log,
id: Id,
comptime name: [:0]const u8,
) void {
emitWithName(log, id, .event, name);
}
/// Marks the beginning of a time interval.
/// Use this with intervalEnd to measure the duration of operations.
/// The same ID must be used for both the begin and end calls.
/// Intervals appear as horizontal bars in Instruments timeline.
///
/// https://developer.apple.com/documentation/os/os_signpost_interval_begin?language=objc
pub fn intervalBegin(log: *Log, id: Id, comptime name: [:0]const u8) void {
emitWithName(log, id, .interval_begin, name);
}
/// Marks the end of a time interval.
/// Must be paired with a prior intervalBegin call using the same ID.
/// The name should match the name used in intervalBegin.
/// Instruments will calculate and display the duration between begin and end.
///
/// https://developer.apple.com/documentation/os/os_signpost_interval_end?language=objc
pub fn intervalEnd(log: *Log, id: Id, comptime name: [:0]const u8) void {
emitWithName(log, id, .interval_end, name);
}
/// The internal function to emit a signpost with a specific name.
fn emitWithName(
log: *Log,
id: Id,
typ: Type,
comptime name: [:0]const u8,
) void {
// Init must be called by this point.
assert(__dso_handle != null);
var buf: [2]u8 = @splat(0);
c._os_signpost_emit_with_name_impl(
__dso_handle,
@ptrCast(log),
@intFromEnum(typ),
@intFromEnum(id),
name.ptr,
"".ptr,
&buf,
buf.len,
);
}
/// https://developer.apple.com/documentation/os/os_signpost_id_t?language=objc
pub const Id = enum(u64) {
null = 0, // OS_SIGNPOST_ID_NULL
invalid = 0xFFFFFFFFFFFFFFFF, // OS_SIGNPOST_ID_INVALID
exclusive = 0xEEEEB0B5B2B2EEEE, // OS_SIGNPOST_ID_EXCLUSIVE
_,
/// Generates a new signpost ID for use with signpost operations.
/// The ID is unique for the given log handle and can be used to track
/// asynchronous operations or mark specific points of interest in the code.
/// Returns a unique signpost ID that can be used with os_signpost functions.
///
/// https://developer.apple.com/documentation/os/os_signpost_id_generate?language=objc
pub fn generate(log: *Log) Id {
return @enumFromInt(c.os_signpost_id_generate(@ptrCast(log)));
}
/// Creates a signpost ID based on a pointer value.
/// This is useful for tracking operations associated with a specific object
/// or memory location. The same pointer will always generate the same ID
/// for a given log handle, allowing correlation of signpost events.
/// Pass null to get the null signpost ID.
///
/// https://developer.apple.com/documentation/os/os_signpost_id_for_pointer?language=objc
pub fn forPointer(log: *Log, ptr: ?*anyopaque) Id {
return @enumFromInt(c.os_signpost_id_make_with_pointer(
@ptrCast(log),
@ptrCast(ptr),
));
}
test "generate ID" {
// We can't really test the return value because it may return null
// if signposts are disabled.
const id: Id = .generate(Log.create("com.mitchellh.ghostty", "test"));
try std.testing.expect(id != .invalid);
}
test "generate ID for pointer" {
var foo: usize = 0x1234;
const id: Id = .forPointer(Log.create("com.mitchellh.ghostty", "test"), &foo);
try std.testing.expect(id != .null);
}
};
/// https://developer.apple.com/documentation/os/ossignposttype?language=objc
pub const Type = enum(u8) {
event = 0, // OS_SIGNPOST_EVENT
interval_begin = 1, // OS_SIGNPOST_INTERVAL_BEGIN
interval_end = 2, // OS_SIGNPOST_INTERVAL_END
pub const mask: u8 = 0x03; // OS_SIGNPOST_TYPE_MASK
};
/// Special os_log category values that surface in Instruments and other
/// tooling.
pub const Category = struct {
/// Points of Interest appear as a dedicated track in Instruments.
/// Use this for high-level application events that help understand
/// the flow of your application.
pub const points_of_interest: [:0]const u8 = "PointsOfInterest";
/// Dynamic Tracing category enables runtime-configurable logging.
/// Signposts in this category can be enabled/disabled dynamically
/// without recompiling.
pub const dynamic_tracing: [:0]const u8 = "DynamicTracing";
/// Dynamic Stack Tracing category captures call stacks at signpost
/// events. This provides deeper debugging information but has higher
/// performance overhead.
pub const dynamic_stack_tracing: [:0]const u8 = "DynamicStackTracing";
};
test {
_ = Id;
}
test enabled {
_ = enabled(Log.create("com.mitchellh.ghostty", "test"));
}
test "intervals" {
init();
const log = Log.create("com.mitchellh.ghostty", "test");
defer log.release();
// Test that we can begin and end an interval
const id = Id.generate(log);
intervalBegin(log, id, "Test Interval");
}

View File

@ -1,4 +1,5 @@
#include <os/log.h> #include <os/log.h>
#include <os/signpost.h>
// A wrapper so we can use the os_log_with_type macro. // A wrapper so we can use the os_log_with_type macro.
void zig_os_log_with_type( void zig_os_log_with_type(

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-05-19 11:34+0300\n" "PO-Revision-Date: 2025-05-19 11:34+0300\n"
"Last-Translator: Damyan Bogoev <damyan.bogoev@gmail.com>\n" "Last-Translator: Damyan Bogoev <damyan.bogoev@gmail.com>\n"
"Language-Team: Bulgarian <dict@ludost.net>\n" "Language-Team: Bulgarian <dict@ludost.net>\n"
@ -236,7 +236,7 @@ msgstr ""
"Поставянето на този текст в терминала може да е опасно, тъй като изглежда, " "Поставянето на този текст в терминала може да е опасно, тъй като изглежда, "
"че може да бъдат изпълнени някои команди." "че може да бъдат изпълнени някои команди."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Затвори" msgstr "Затвори"
@ -276,6 +276,14 @@ msgstr "Текущият процес в това разделяне ще бъд
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Копирано в клипборда" msgstr "Копирано в клипборда"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Главно меню" msgstr "Главно меню"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-20 08:07+0100\n" "PO-Revision-Date: 2025-03-20 08:07+0100\n"
"Last-Translator: Francesc Arpi <francesc.arpi@gmail.com>\n" "Last-Translator: Francesc Arpi <francesc.arpi@gmail.com>\n"
"Language-Team: \n" "Language-Team: \n"
@ -236,7 +236,7 @@ msgstr ""
"Enganxar aquest text al terminal pot ser perillós, ja que sembla que es " "Enganxar aquest text al terminal pot ser perillós, ja que sembla que es "
"podrien executar algunes ordres." "podrien executar algunes ordres."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Tanca" msgstr "Tanca"
@ -276,6 +276,14 @@ msgstr "El procés actualment en execució en aquesta divisió es tancarà."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Copiat al porta-retalls" msgstr "Copiat al porta-retalls"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Menú principal" msgstr "Menú principal"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -228,7 +228,7 @@ msgid ""
"commands may be executed." "commands may be executed."
msgstr "" msgstr ""
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "" msgstr ""
@ -268,6 +268,14 @@ msgstr ""
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "" msgstr ""
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "" msgstr ""

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-06 14:57+0100\n" "PO-Revision-Date: 2025-03-06 14:57+0100\n"
"Last-Translator: Robin <r@rpfaeffle.com>\n" "Last-Translator: Robin <r@rpfaeffle.com>\n"
"Language-Team: German <translation-team-de@lists.sourceforge.net>\n" "Language-Team: German <translation-team-de@lists.sourceforge.net>\n"
@ -235,7 +235,7 @@ msgstr ""
"Diesen Text in das Terminal einzufügen könnte möglicherweise gefährlich " "Diesen Text in das Terminal einzufügen könnte möglicherweise gefährlich "
"sein. Es scheint, dass Anweisungen ausgeführt werden könnten." "sein. Es scheint, dass Anweisungen ausgeführt werden könnten."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Schließen" msgstr "Schließen"
@ -275,6 +275,14 @@ msgstr "Der aktuell laufende Prozess in diesem geteilten Fenster wird beendet."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "In die Zwischenablage kopiert" msgstr "In die Zwischenablage kopiert"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Hauptmenü" msgstr "Hauptmenü"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-05-19 20:17-0300\n" "PO-Revision-Date: 2025-05-19 20:17-0300\n"
"Last-Translator: Alan Moyano <alanmoyano203@gmail.com>\n" "Last-Translator: Alan Moyano <alanmoyano203@gmail.com>\n"
"Language-Team: Argentinian <es@tp.org.es>\n" "Language-Team: Argentinian <es@tp.org.es>\n"
@ -236,7 +236,7 @@ msgstr ""
"Pegar este texto en la terminal puede ser peligroso ya que parece que " "Pegar este texto en la terminal puede ser peligroso ya que parece que "
"algunos comandos podrían ejecutarse." "algunos comandos podrían ejecutarse."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Cerrar" msgstr "Cerrar"
@ -276,6 +276,14 @@ msgstr "El proceso actualmente en ejecución en esta división será terminado."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Copiado al portapapeles" msgstr "Copiado al portapapeles"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Menú principal" msgstr "Menú principal"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-28 17:46+0200\n" "PO-Revision-Date: 2025-03-28 17:46+0200\n"
"Last-Translator: Miguel Peredo <miguelp@quientienemail.com>\n" "Last-Translator: Miguel Peredo <miguelp@quientienemail.com>\n"
"Language-Team: Spanish <es@tp.org.es>\n" "Language-Team: Spanish <es@tp.org.es>\n"
@ -236,7 +236,7 @@ msgstr ""
"Pegar este texto en la terminal puede ser peligroso ya que parece que " "Pegar este texto en la terminal puede ser peligroso ya que parece que "
"algunos comandos podrían ejecutarse." "algunos comandos podrían ejecutarse."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Cerrar" msgstr "Cerrar"
@ -276,6 +276,14 @@ msgstr "El proceso actualmente en ejecución en esta división será terminado."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Copiado al portapapeles" msgstr "Copiado al portapapeles"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Menú principal" msgstr "Menú principal"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-22 09:31+0100\n" "PO-Revision-Date: 2025-03-22 09:31+0100\n"
"Last-Translator: Kirwiisp <swiip__@hotmail.com>\n" "Last-Translator: Kirwiisp <swiip__@hotmail.com>\n"
"Language-Team: French <traduc@traduc.org>\n" "Language-Team: French <traduc@traduc.org>\n"
@ -237,7 +237,7 @@ msgstr ""
"Coller ce texte dans le terminal pourrait être dangereux, il semblerait que " "Coller ce texte dans le terminal pourrait être dangereux, il semblerait que "
"certaines commandes pourraient être exécutées." "certaines commandes pourraient être exécutées."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Fermer" msgstr "Fermer"
@ -277,6 +277,14 @@ msgstr "Le processus en cours dans ce panneau va être arrêté."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Copié dans le presse-papiers" msgstr "Copié dans le presse-papiers"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Menu principal" msgstr "Menu principal"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-06-29 21:15+0100\n" "PO-Revision-Date: 2025-06-29 21:15+0100\n"
"Last-Translator: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>\n" "Last-Translator: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>\n"
"Language-Team: Irish <gaeilge-gnulinux@lists.sourceforge.net>\n" "Language-Team: Irish <gaeilge-gnulinux@lists.sourceforge.net>\n"
@ -237,7 +237,7 @@ msgstr ""
"Dfhéadfadh sé a bheith contúirteach an téacs seo a ghreamú isteach sa " "Dfhéadfadh sé a bheith contúirteach an téacs seo a ghreamú isteach sa "
"teirminéal, toisc go d'fhéadfadh roinnt orduithe a fhorghníomhú." "teirminéal, toisc go d'fhéadfadh roinnt orduithe a fhorghníomhú."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Dún" msgstr "Dún"
@ -278,6 +278,14 @@ msgstr ""
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Cóipeáilte chuig an ghearrthaisce" msgstr "Cóipeáilte chuig an ghearrthaisce"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Príomh-Roghchlár" msgstr "Príomh-Roghchlár"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-13 00:00+0000\n" "PO-Revision-Date: 2025-03-13 00:00+0000\n"
"Last-Translator: Sl (Shahaf Levi), Sl's Repository Ltd <ghostty@slsrepo." "Last-Translator: Sl (Shahaf Levi), Sl's Repository Ltd <ghostty@slsrepo."
"com>\n" "com>\n"
@ -234,7 +234,7 @@ msgstr ""
"הדבקת טקסט זה במסוף עלולה להיות מסוכנת, מכיוון שככל הנראה היא תוביל להרצה של " "הדבקת טקסט זה במסוף עלולה להיות מסוכנת, מכיוון שככל הנראה היא תוביל להרצה של "
"פקודות מסוימות." "פקודות מסוימות."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "סגירה" msgstr "סגירה"
@ -274,6 +274,14 @@ msgstr "התהליך שרץ כרגע בפיצול זה יסתיים."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "הועתק ללוח ההעתקה" msgstr "הועתק ללוח ההעתקה"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "תפריט ראשי" msgstr "תפריט ראשי"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-20 15:19+0700\n" "PO-Revision-Date: 2025-03-20 15:19+0700\n"
"Last-Translator: Satrio Bayu Aji <halosatrio@gmail.com>\n" "Last-Translator: Satrio Bayu Aji <halosatrio@gmail.com>\n"
"Language-Team: Indonesian <translation-team-id@lists.sourceforge.net>\n" "Language-Team: Indonesian <translation-team-id@lists.sourceforge.net>\n"
@ -235,7 +235,7 @@ msgstr ""
"Menempelkan teks ini ke terminal mungkin berbahaya karena sepertinya " "Menempelkan teks ini ke terminal mungkin berbahaya karena sepertinya "
"beberapa perintah mungkin dijalankan." "beberapa perintah mungkin dijalankan."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Tutup" msgstr "Tutup"
@ -275,6 +275,14 @@ msgstr "Proses yang sedang berjalan dalam belahan ini akan diakhiri."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Disalin ke papan klip" msgstr "Disalin ke papan klip"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Menu utama" msgstr "Menu utama"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-21 00:08+0900\n" "PO-Revision-Date: 2025-03-21 00:08+0900\n"
"Last-Translator: Lon Sagisawa <lon@sagisawa.me>\n" "Last-Translator: Lon Sagisawa <lon@sagisawa.me>\n"
"Language-Team: Japanese\n" "Language-Team: Japanese\n"
@ -237,7 +237,7 @@ msgstr ""
"このテキストには実行可能なコマンドが含まれており、ターミナルに貼り付けるのは" "このテキストには実行可能なコマンドが含まれており、ターミナルに貼り付けるのは"
"危険な可能性があります。" "危険な可能性があります。"
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "閉じる" msgstr "閉じる"
@ -277,6 +277,14 @@ msgstr "分割ウィンドウ内のすべてのプロセスが終了します。
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "クリップボードにコピーしました" msgstr "クリップボードにコピーしました"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "メインメニュー" msgstr "メインメニュー"

View File

@ -7,9 +7,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-31 03:08+0200\n" "PO-Revision-Date: 2025-07-09 16:11-0400\n"
"Last-Translator: Ruben Engelbrecht <hey@rme.gg>\n" "Last-Translator: Hojin You <dev.hojin@gmail.com>\n"
"Language-Team: Korean <translation-team-ko@googlegroups.com>\n" "Language-Team: Korean <translation-team-ko@googlegroups.com>\n"
"Language: ko\n" "Language: ko\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -87,7 +87,7 @@ msgstr "오른쪽으로 창 나누기"
#: src/apprt/gtk/ui/1.5/command-palette.blp:16 #: src/apprt/gtk/ui/1.5/command-palette.blp:16
msgid "Execute a command…" msgid "Execute a command…"
msgstr "" msgstr "명령을 실행하세요…"
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
@ -160,7 +160,7 @@ msgstr "설정 열기"
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
msgid "Command Palette" msgid "Command Palette"
msgstr "" msgstr "명령 팔레트"
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
msgid "Terminal Inspector" msgid "Terminal Inspector"
@ -208,12 +208,12 @@ msgstr "허용"
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81 #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
msgid "Remember choice for this split" msgid "Remember choice for this split"
msgstr "" msgstr "이 분할에 대한 선택 기억하기"
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82 #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
msgid "Reload configuration to show this prompt again" msgid "Reload configuration to show this prompt again"
msgstr "" msgstr "이 창을 다시 보려면 설정을 다시 불러오세요"
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7 #: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
@ -236,7 +236,7 @@ msgstr ""
"이 텍스트를 터미널에 붙여넣는 것은 위험할 수 있습니다. 일부 명령이 실행될 수 " "이 텍스트를 터미널에 붙여넣는 것은 위험할 수 있습니다. 일부 명령이 실행될 수 "
"있는 것으로 보입니다." "있는 것으로 보입니다."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "닫기" msgstr "닫기"
@ -276,6 +276,14 @@ msgstr "이 분할에서 현재 실행 중인 프로세스가 종료됩니다."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "클립보드에 복사됨" msgstr "클립보드에 복사됨"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "메인 메뉴" msgstr "메인 메뉴"
@ -285,9 +293,8 @@ msgid "View Open Tabs"
msgstr "열린 탭 보기" msgstr "열린 탭 보기"
#: src/apprt/gtk/Window.zig:266 #: src/apprt/gtk/Window.zig:266
#, fuzzy
msgid "New Split" msgid "New Split"
msgstr "나누기" msgstr "새 분할"
#: src/apprt/gtk/Window.zig:329 #: src/apprt/gtk/Window.zig:329
msgid "" msgid ""

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-23 14:17+0100\n" "PO-Revision-Date: 2025-03-23 14:17+0100\n"
"Last-Translator: Andrej Daskalov <andrej.daskalov@gmail.com>\n" "Last-Translator: Andrej Daskalov <andrej.daskalov@gmail.com>\n"
"Language-Team: Macedonian\n" "Language-Team: Macedonian\n"
@ -236,7 +236,7 @@ msgstr ""
"Вметнувањето на овој текст во терминалот може да биде опасно, бидејќи " "Вметнувањето на овој текст во терминалот може да биде опасно, бидејќи "
"изгледа како да ќе се извршат одредени команди." "изгледа како да ќе се извршат одредени команди."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Затвори" msgstr "Затвори"
@ -276,6 +276,14 @@ msgstr "Процесот кој моментално се извршува во
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Копирано во привремена меморија" msgstr "Копирано во привремена меморија"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Главно мени" msgstr "Главно мени"

View File

@ -10,7 +10,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-04-14 16:25+0200\n" "PO-Revision-Date: 2025-04-14 16:25+0200\n"
"Last-Translator: cryptocode <cryptocode@zolo.io>\n" "Last-Translator: cryptocode <cryptocode@zolo.io>\n"
"Language-Team: Norwegian Bokmal <l10n-no@lister.huftis.org>\n" "Language-Team: Norwegian Bokmal <l10n-no@lister.huftis.org>\n"
@ -239,7 +239,7 @@ msgstr ""
"Det ser ut som at kommandoer vil bli kjørt hvis du limer inn dette, vurder " "Det ser ut som at kommandoer vil bli kjørt hvis du limer inn dette, vurder "
"om du mener det er trygt." "om du mener det er trygt."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Lukk" msgstr "Lukk"
@ -279,6 +279,14 @@ msgstr "Den kjørende prosessen for denne splitten vil bli avsluttet."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Kopiert til utklippstavlen" msgstr "Kopiert til utklippstavlen"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Hovedmeny" msgstr "Hovedmeny"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-07-12 06:54+0200\n" "PO-Revision-Date: 2025-07-12 06:54+0200\n"
"Last-Translator: Merijntje Tak <merijntje@tak.io>\n" "Last-Translator: Merijntje Tak <merijntje@tak.io>\n"
"Language-Team: Dutch <vertaling@vrijschrift.org>\n" "Language-Team: Dutch <vertaling@vrijschrift.org>\n"
@ -237,7 +237,7 @@ msgstr ""
"Het plakken van deze tekst in de terminal is mogelijk gevaarlijk, omdat het " "Het plakken van deze tekst in de terminal is mogelijk gevaarlijk, omdat het "
"lijkt op een commando dat uitgevoerd kan worden." "lijkt op een commando dat uitgevoerd kan worden."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Afsluiten" msgstr "Afsluiten"
@ -278,6 +278,14 @@ msgstr ""
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Gekopieerd naar klembord" msgstr "Gekopieerd naar klembord"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Hoofdmenu" msgstr "Hoofdmenu"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-17 12:15+0100\n" "PO-Revision-Date: 2025-03-17 12:15+0100\n"
"Last-Translator: Bartosz Sokorski <b.sokorski@gmail.com>\n" "Last-Translator: Bartosz Sokorski <b.sokorski@gmail.com>\n"
"Language-Team: Polish <translation-team-pl@lists.sourceforge.net>\n" "Language-Team: Polish <translation-team-pl@lists.sourceforge.net>\n"
@ -238,7 +238,7 @@ msgstr ""
"Wklejenie tego tekstu do terminala może być niebezpieczne, ponieważ może " "Wklejenie tego tekstu do terminala może być niebezpieczne, ponieważ może "
"spowodować wykonanie komend." "spowodować wykonanie komend."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Zamknij" msgstr "Zamknij"
@ -278,6 +278,14 @@ msgstr "Wszyskie trwające procesy w obecnym podziale zostaną zakończone."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Skopiowano do schowka" msgstr "Skopiowano do schowka"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Menu główne" msgstr "Menu główne"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-06-20 10:19-0300\n" "PO-Revision-Date: 2025-06-20 10:19-0300\n"
"Last-Translator: Mário Victor Ribeiro Silva <mariovictorrs@gmail.com>\n" "Last-Translator: Mário Victor Ribeiro Silva <mariovictorrs@gmail.com>\n"
"Language-Team: Brazilian Portuguese <ldpbr-translation@lists.sourceforge." "Language-Team: Brazilian Portuguese <ldpbr-translation@lists.sourceforge."
@ -238,7 +238,7 @@ msgstr ""
"Colar esse texto em um terminal pode ser perigoso, pois parece que alguns " "Colar esse texto em um terminal pode ser perigoso, pois parece que alguns "
"comandos podem ser executados." "comandos podem ser executados."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Fechar" msgstr "Fechar"
@ -278,6 +278,14 @@ msgstr "O processo atual rodando nessa divisão será finalizado."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Copiado para a área de transferência" msgstr "Copiado para a área de transferência"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Menu Principal" msgstr "Menu Principal"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-24 00:01+0500\n" "PO-Revision-Date: 2025-03-24 00:01+0500\n"
"Last-Translator: blackzeshi <sergey_zhuzhgov@mail.ru>\n" "Last-Translator: blackzeshi <sergey_zhuzhgov@mail.ru>\n"
"Language-Team: Russian <gnu@d07.ru>\n" "Language-Team: Russian <gnu@d07.ru>\n"
@ -237,7 +237,7 @@ msgstr ""
"Вставка этого текста в терминал может быть опасной. Это выглядит как " "Вставка этого текста в терминал может быть опасной. Это выглядит как "
"команды, которые могут быть исполнены." "команды, которые могут быть исполнены."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Закрыть" msgstr "Закрыть"
@ -277,6 +277,14 @@ msgstr "Процесс, работающий в этой сплит-област
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Скопировано в буфер обмена" msgstr "Скопировано в буфер обмена"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Главное меню" msgstr "Главное меню"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-24 22:01+0300\n" "PO-Revision-Date: 2025-03-24 22:01+0300\n"
"Last-Translator: Emir SARI <emir_sari@icloud.com>\n" "Last-Translator: Emir SARI <emir_sari@icloud.com>\n"
"Language-Team: Turkish\n" "Language-Team: Turkish\n"
@ -237,7 +237,7 @@ msgstr ""
"Bu metni uçbirime yapıştırmak tehlikeli olabilir; çünkü bir komut " "Bu metni uçbirime yapıştırmak tehlikeli olabilir; çünkü bir komut "
"yürütülebilecekmiş gibi duruyor." "yürütülebilecekmiş gibi duruyor."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Kapat" msgstr "Kapat"
@ -277,6 +277,14 @@ msgstr "Bu bölmedeki şu anda çalışan süreç sonlandırılacaktır."
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Panoya kopyalandı" msgstr "Panoya kopyalandı"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Ana Menü" msgstr "Ana Menü"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-03-16 20:16+0200\n" "PO-Revision-Date: 2025-03-16 20:16+0200\n"
"Last-Translator: Danylo Zalizchuk <danilmail0110@gmail.com>\n" "Last-Translator: Danylo Zalizchuk <danilmail0110@gmail.com>\n"
"Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n" "Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n"
@ -238,7 +238,7 @@ msgstr ""
"Вставка цього тексту в термінал може бути небезпечною, оскільки виглядає " "Вставка цього тексту в термінал може бути небезпечною, оскільки виглядає "
"так, ніби деякі команди можуть бути виконані." "так, ніби деякі команди можуть бути виконані."
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "Закрити" msgstr "Закрити"
@ -279,6 +279,14 @@ msgstr ""
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Скопійовано в буфер обміну" msgstr "Скопійовано в буфер обміну"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "Головне меню" msgstr "Головне меню"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n" "Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n"
"POT-Creation-Date: 2025-07-08 15:13-0500\n" "POT-Creation-Date: 2025-07-11 22:56-0500\n"
"PO-Revision-Date: 2025-02-27 09:16+0100\n" "PO-Revision-Date: 2025-02-27 09:16+0100\n"
"Last-Translator: Leah <hi@pluie.me>\n" "Last-Translator: Leah <hi@pluie.me>\n"
"Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n" "Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n"
@ -229,7 +229,7 @@ msgid ""
"commands may be executed." "commands may be executed."
msgstr "将以下内容粘贴至终端内将可能执行有害命令。" msgstr "将以下内容粘贴至终端内将可能执行有害命令。"
#: src/apprt/gtk/CloseDialog.zig:47 #: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2520
msgid "Close" msgid "Close"
msgstr "关闭" msgstr "关闭"
@ -269,6 +269,14 @@ msgstr "分屏内正在运行中的进程将被终止。"
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "已复制至剪贴板" msgstr "已复制至剪贴板"
#: src/apprt/gtk/Surface.zig:2514
msgid "Command succeeded"
msgstr ""
#: src/apprt/gtk/Surface.zig:2516
msgid "Command failed"
msgstr ""
#: src/apprt/gtk/Window.zig:216 #: src/apprt/gtk/Window.zig:216
msgid "Main Menu" msgid "Main Menu"
msgstr "主菜单" msgstr "主菜单"

View File

@ -1011,8 +1011,18 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void {
log.warn("abnormal process exit detected, showing error message", .{}); log.warn("abnormal process exit detected, showing error message", .{});
// Update our terminal to note the abnormal exit. In the future we // Try and show a GUI message. If it returns true, don't do anything else.
// may want the apprt to handle this to show some native GUI element. if (self.rt_app.performAction(
.{ .surface = self },
.show_child_exited,
info,
) catch |err| gui: {
log.err("error trying to show native child exited GUI err={}", .{err});
break :gui false;
}) return;
// If a native GUI notification was not showm. update our terminal to
// note the abnormal exit.
self.childExitedAbnormally(info) catch |err| { self.childExitedAbnormally(info) catch |err| {
log.err("error handling abnormal child exit err={}", .{err}); log.err("error handling abnormal child exit err={}", .{err});
return; return;
@ -1028,6 +1038,18 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void {
// surface then they will see this message and know the process has // surface then they will see this message and know the process has
// completed. // completed.
terminal: { terminal: {
// First try and show a native GUI message.
if (self.rt_app.performAction(
.{ .surface = self },
.show_child_exited,
info,
) catch |err| gui: {
log.err("error trying to show native child exited GUI err={}", .{err});
break :gui false;
}) break :terminal;
// If the native GUI can't be shown, display a text message in the
// terminal.
self.renderer_state.mutex.lock(); self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock(); defer self.renderer_state.mutex.unlock();
const t: *terminal.Terminal = self.renderer_state.terminal; const t: *terminal.Terminal = self.renderer_state.terminal;

View File

@ -272,6 +272,9 @@ pub const Action = union(Key) {
/// apprt. /// apprt.
open_url: OpenUrl, open_url: OpenUrl,
/// Show a native GUI notification that the child process has exited.
show_child_exited: apprt.surface.Message.ChildExited,
/// Sync with: ghostty_action_tag_e /// Sync with: ghostty_action_tag_e
pub const Key = enum(c_int) { pub const Key = enum(c_int) {
quit, quit,
@ -323,6 +326,7 @@ pub const Action = union(Key) {
redo, redo,
check_for_updates, check_for_updates,
open_url, open_url,
show_child_exited,
}; };
/// Sync with: ghostty_action_u /// Sync with: ghostty_action_u

View File

@ -521,6 +521,7 @@ pub fn performAction(
.ring_bell => try self.ringBell(target), .ring_bell => try self.ringBell(target),
.toggle_command_palette => try self.toggleCommandPalette(target), .toggle_command_palette => try self.toggleCommandPalette(target),
.open_url => self.openUrl(value), .open_url => self.openUrl(value),
.show_child_exited => return try self.showChildExited(target, value),
// Unimplemented // Unimplemented
.close_all_windows, .close_all_windows,
@ -846,6 +847,13 @@ fn toggleCommandPalette(_: *App, target: apprt.Target) !void {
} }
} }
fn showChildExited(_: *App, target: apprt.Target, value: apprt.surface.Message.ChildExited) error{}!bool {
switch (target) {
.app => return false,
.surface => |surface| return try surface.rt_surface.showChildExited(value),
}
}
fn quitTimer(self: *App, mode: apprt.action.QuitTimer) void { fn quitTimer(self: *App, mode: apprt.action.QuitTimer) void {
switch (mode) { switch (mode) {
.start => self.startQuitTimer(), .start => self.startQuitTimer(),

View File

@ -2503,3 +2503,40 @@ fn gtkStreamError(media_file: *gtk.MediaFile, _: *gobject.ParamSpec, _: ?*anyopa
fn gtkStreamEnded(media_file: *gtk.MediaFile, _: *gobject.ParamSpec, _: ?*anyopaque) callconv(.c) void { fn gtkStreamEnded(media_file: *gtk.MediaFile, _: *gobject.ParamSpec, _: ?*anyopaque) callconv(.c) void {
media_file.unref(); media_file.unref();
} }
/// Show native GUI element with a notification that the child process has
/// closed. Return `true` if we are able to show the GUI notification, and
/// `false` if we are not.
pub fn showChildExited(self: *Surface, info: apprt.surface.Message.ChildExited) error{}!bool {
if (!adw_version.supportsBanner()) return false;
const warning_text, const css_class = if (info.exit_code == 0)
.{ i18n._("Command succeeded"), "child_exited_normally" }
else
.{ i18n._("Command failed"), "child_exited_abnormally" };
const banner = adw.Banner.new(warning_text);
banner.setRevealed(1);
banner.setButtonLabel(i18n._("Close"));
_ = adw.Banner.signals.button_clicked.connect(
banner,
*Surface,
showChildExitedButtonClosed,
self,
.{},
);
const banner_widget = banner.as(gtk.Widget);
banner_widget.setHalign(.fill);
banner_widget.setValign(.end);
banner_widget.addCssClass(css_class);
self.overlay.addOverlay(banner_widget);
return true;
}
fn showChildExitedButtonClosed(_: *adw.Banner, self: *Surface) callconv(.c) void {
self.close(false);
}

View File

@ -93,3 +93,15 @@ window.ssd.no-border-radius {
margin-left: 4px; margin-left: 4px;
margin-right: 8px; margin-right: 8px;
} }
banner.child_exited_normally revealer widget {
background-color: rgba(38, 162, 105, 0.5);
/* after GTK 4.16 is a requirement, switch to the following:
/* background-color: color-mix(in srgb, var(--success-bg-color), transparent 50%); */
}
banner.child_exited_abnormally revealer widget {
background-color: rgba(192, 28, 40, 0.5);
/* after GTK 4.16 is a requirement, switch to the following:
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent 50%); */
}

View File

@ -98,7 +98,7 @@ pub const Message = union(enum) {
// This enum is a placeholder for future title styles. // This enum is a placeholder for future title styles.
}; };
pub const ChildExited = struct { pub const ChildExited = extern struct {
exit_code: u32, exit_code: u32,
runtime_ms: u64, runtime_ms: u64,
}; };

View File

@ -1,34 +0,0 @@
#!/usr/bin/env bash
#
# This is a trivial helper script to help run the codepoint-width benchmark.
# You probably want to tweak this script depending on what you're
# trying to measure.
# Options:
# - "ascii", uniform random ASCII bytes
# - "utf8", uniform random unicode characters, encoded as utf8
# - "rand", pure random data, will contain many invalid code sequences.
DATA="utf8"
SIZE="25000000"
# Add additional arguments
ARGS=""
# Generate the benchmark input ahead of time so it's not included in the time.
./zig-out/bin/bench-stream --mode=gen-$DATA | head -c $SIZE > /tmp/ghostty_bench_data
#cat ~/Downloads/JAPANESEBIBLE.txt > /tmp/ghostty_bench_data
# Uncomment to instead use the contents of `stream.txt` as input.
# yes $(cat ./stream.txt) | head -c $SIZE > /tmp/ghostty_bench_data
hyperfine \
--warmup 10 \
-n noop \
"./zig-out/bin/bench-codepoint-width --mode=noop${ARGS} </tmp/ghostty_bench_data" \
-n wcwidth \
"./zig-out/bin/bench-codepoint-width --mode=wcwidth${ARGS} </tmp/ghostty_bench_data" \
-n table \
"./zig-out/bin/bench-codepoint-width --mode=table${ARGS} </tmp/ghostty_bench_data" \
-n simd \
"./zig-out/bin/bench-codepoint-width --mode=simd${ARGS} </tmp/ghostty_bench_data"

View File

@ -1,204 +0,0 @@
//! This benchmark tests the throughput of codepoint width calculation.
//! This is a common operation in terminal character printing and the
//! motivating factor to write this benchmark was discovering that our
//! codepoint width function was 30% of the runtime of every character
//! print.
//!
//! This will consume all of the available stdin, so you should run it
//! with `head` in a pipe to restrict. For example, to test ASCII input:
//!
//! bench-stream --mode=gen-ascii | head -c 50M | bench-codepoint-width --mode=ziglyph
//!
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const ziglyph = @import("ziglyph");
const cli = @import("../cli.zig");
const simd = @import("../simd/main.zig");
const table = @import("../unicode/main.zig").table;
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const Args = struct {
mode: Mode = .noop,
/// The size for read buffers. Doesn't usually need to be changed. The
/// main point is to make this runtime known so we can avoid compiler
/// optimizations.
@"buffer-size": usize = 4096,
/// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null,
pub fn deinit(self: *Args) void {
if (self._arena) |arena| arena.deinit();
self.* = undefined;
}
};
const Mode = enum {
/// The baseline mode copies the data from the fd into a buffer. This
/// is used to show the minimal overhead of reading the fd into memory
/// and establishes a baseline for the other modes.
noop,
/// libc wcwidth
wcwidth,
/// Use ziglyph library to calculate the display width of each codepoint.
ziglyph,
/// Our SIMD implementation.
simd,
/// Test our lookup table implementation.
table,
};
pub const std_options: std.Options = .{
.log_level = .debug,
};
pub fn main() !void {
// We want to use the c allocator because it is much faster than GPA.
const alloc = std.heap.c_allocator;
// Parse our args
var args: Args = .{};
defer args.deinit();
{
var iter = try cli.args.argsIterator(alloc);
defer iter.deinit();
try cli.args.parse(Args, alloc, &args, &iter);
}
const reader = std.io.getStdIn().reader();
const buf = try alloc.alloc(u8, args.@"buffer-size");
// Handle the modes that do not depend on terminal state first.
switch (args.mode) {
.noop => try benchNoop(reader, buf),
.wcwidth => try benchWcwidth(reader, buf),
.ziglyph => try benchZiglyph(reader, buf),
.simd => try benchSimd(reader, buf),
.table => try benchTable(reader, buf),
}
}
noinline fn benchNoop(
reader: anytype,
buf: []u8,
) !void {
var d: UTF8Decoder = .{};
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
// Using stream.next directly with a for loop applies a naive
// scalar approach.
for (buf[0..n]) |c| {
_ = d.next(c);
}
}
}
extern "c" fn wcwidth(c: u32) c_int;
noinline fn benchWcwidth(
reader: anytype,
buf: []u8,
) !void {
var d: UTF8Decoder = .{};
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
// Using stream.next directly with a for loop applies a naive
// scalar approach.
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp| {
const width = wcwidth(cp);
// Write the width to the buffer to avoid it being compiled away
buf[0] = @intCast(width);
}
}
}
}
noinline fn benchTable(
reader: anytype,
buf: []u8,
) !void {
var d: UTF8Decoder = .{};
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
// Using stream.next directly with a for loop applies a naive
// scalar approach.
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp| {
// This is the same trick we do in terminal.zig so we
// keep it here.
const width = if (cp <= 0xFF) 1 else table.get(@intCast(cp)).width;
// Write the width to the buffer to avoid it being compiled away
buf[0] = @intCast(width);
}
}
}
}
noinline fn benchZiglyph(
reader: anytype,
buf: []u8,
) !void {
var d: UTF8Decoder = .{};
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
// Using stream.next directly with a for loop applies a naive
// scalar approach.
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp| {
const width = ziglyph.display_width.codePointWidth(cp, .half);
// Write the width to the buffer to avoid it being compiled away
buf[0] = @intCast(width);
}
}
}
}
noinline fn benchSimd(
reader: anytype,
buf: []u8,
) !void {
var d: UTF8Decoder = .{};
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
// Using stream.next directly with a for loop applies a naive
// scalar approach.
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp| {
const width = simd.codepointWidth(cp);
// Write the width to the buffer to avoid it being compiled away
buf[0] = @intCast(width);
}
}
}
}

View File

@ -1,33 +0,0 @@
#!/usr/bin/env bash
#
# This is a trivial helper script to help run the grapheme-break benchmark.
# You probably want to tweak this script depending on what you're
# trying to measure.
# Options:
# - "ascii", uniform random ASCII bytes
# - "utf8", uniform random unicode characters, encoded as utf8
# - "rand", pure random data, will contain many invalid code sequences.
DATA="utf8"
SIZE="25000000"
# Add additional arguments
ARGS=""
# Generate the benchmark input ahead of time so it's not included in the time.
./zig-out/bin/bench-stream --mode=gen-$DATA | head -c $SIZE > /tmp/ghostty_bench_data
#cat ~/Downloads/JAPANESEBIBLE.txt > /tmp/ghostty_bench_data
# Uncomment to instead use the contents of `stream.txt` as input.
# yes $(cat ./stream.txt) | head -c $SIZE > /tmp/ghostty_bench_data
hyperfine \
--warmup 10 \
-n noop \
"./zig-out/bin/bench-grapheme-break --mode=noop${ARGS} </tmp/ghostty_bench_data" \
-n ziglyph \
"./zig-out/bin/bench-grapheme-break --mode=ziglyph${ARGS} </tmp/ghostty_bench_data" \
-n table \
"./zig-out/bin/bench-grapheme-break --mode=table${ARGS} </tmp/ghostty_bench_data"

View File

@ -1,144 +0,0 @@
//! This benchmark tests the throughput of grapheme break calculation.
//! This is a common operation in terminal character printing for terminals
//! that support grapheme clustering.
//!
//! This will consume all of the available stdin, so you should run it
//! with `head` in a pipe to restrict. For example, to test ASCII input:
//!
//! bench-stream --mode=gen-ascii | head -c 50M | bench-grapheme-break --mode=ziglyph
//!
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const ziglyph = @import("ziglyph");
const cli = @import("../cli.zig");
const simd = @import("../simd/main.zig");
const unicode = @import("../unicode/main.zig");
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const Args = struct {
mode: Mode = .noop,
/// The size for read buffers. Doesn't usually need to be changed. The
/// main point is to make this runtime known so we can avoid compiler
/// optimizations.
@"buffer-size": usize = 4096,
/// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null,
pub fn deinit(self: *Args) void {
if (self._arena) |arena| arena.deinit();
self.* = undefined;
}
};
const Mode = enum {
/// The baseline mode copies the data from the fd into a buffer. This
/// is used to show the minimal overhead of reading the fd into memory
/// and establishes a baseline for the other modes.
noop,
/// Use ziglyph library to calculate the display width of each codepoint.
ziglyph,
/// Ghostty's table-based approach.
table,
};
pub const std_options: std.Options = .{
.log_level = .debug,
};
pub fn main() !void {
// We want to use the c allocator because it is much faster than GPA.
const alloc = std.heap.c_allocator;
// Parse our args
var args: Args = .{};
defer args.deinit();
{
var iter = try cli.args.argsIterator(alloc);
defer iter.deinit();
try cli.args.parse(Args, alloc, &args, &iter);
}
const reader = std.io.getStdIn().reader();
const buf = try alloc.alloc(u8, args.@"buffer-size");
// Handle the modes that do not depend on terminal state first.
switch (args.mode) {
.noop => try benchNoop(reader, buf),
.ziglyph => try benchZiglyph(reader, buf),
.table => try benchTable(reader, buf),
}
}
noinline fn benchNoop(
reader: anytype,
buf: []u8,
) !void {
var d: UTF8Decoder = .{};
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
// Using stream.next directly with a for loop applies a naive
// scalar approach.
for (buf[0..n]) |c| {
_ = d.next(c);
}
}
}
noinline fn benchTable(
reader: anytype,
buf: []u8,
) !void {
var d: UTF8Decoder = .{};
var state: unicode.GraphemeBreakState = .{};
var cp1: u21 = 0;
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
// Using stream.next directly with a for loop applies a naive
// scalar approach.
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp2| {
const v = unicode.graphemeBreak(cp1, @intCast(cp2), &state);
buf[0] = @intCast(@intFromBool(v));
cp1 = cp2;
}
}
}
}
noinline fn benchZiglyph(
reader: anytype,
buf: []u8,
) !void {
var d: UTF8Decoder = .{};
var state: u3 = 0;
var cp1: u21 = 0;
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
// Using stream.next directly with a for loop applies a naive
// scalar approach.
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp2| {
const v = ziglyph.graphemeBreak(cp1, @intCast(cp2), &state);
buf[0] = @intCast(@intFromBool(v));
cp1 = cp2;
}
}
}
}

View File

@ -1,16 +0,0 @@
#!/usr/bin/env bash
#
# This is a trivial helper script to help run the page init benchmark.
# You probably want to tweak this script depending on what you're
# trying to measure.
# Uncomment to test with an active terminal state.
# ARGS=" --terminal"
hyperfine \
--warmup 10 \
-n alloc \
"./zig-out/bin/bench-page-init --mode=alloc${ARGS} </tmp/ghostty_bench_data" \
-n pool \
"./zig-out/bin/bench-page-init --mode=pool${ARGS} </tmp/ghostty_bench_data"

View File

@ -1,78 +0,0 @@
//! This benchmark tests the speed to create a terminal "page". This is
//! the internal data structure backing a terminal screen. The creation speed
//! is important because it is one of the primary bottlenecks for processing
//! large amounts of plaintext data.
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const cli = @import("../cli.zig");
const terminal_new = @import("../terminal/main.zig");
const Args = struct {
mode: Mode = .alloc,
/// The number of pages to create sequentially.
count: usize = 10_000,
/// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null,
pub fn deinit(self: *Args) void {
if (self._arena) |arena| arena.deinit();
self.* = undefined;
}
};
const Mode = enum {
/// The default allocation strategy of the structure.
alloc,
/// Use a memory pool to allocate pages from a backing buffer.
pool,
};
pub const std_options: std.Options = .{
.log_level = .debug,
};
pub fn main() !void {
// We want to use the c allocator because it is much faster than GPA.
const alloc = std.heap.c_allocator;
// Parse our args
var args: Args = .{};
defer args.deinit();
{
var iter = try cli.args.argsIterator(alloc);
defer iter.deinit();
try cli.args.parse(Args, alloc, &args, &iter);
}
// Handle the modes that do not depend on terminal state first.
switch (args.mode) {
.alloc => try benchAlloc(args.count),
.pool => try benchPool(alloc, args.count),
}
}
noinline fn benchAlloc(count: usize) !void {
for (0..count) |_| {
_ = try terminal_new.Page.init(terminal_new.page.std_capacity);
}
}
noinline fn benchPool(alloc: Allocator, count: usize) !void {
var list = try terminal_new.PageList.init(
alloc,
terminal_new.page.std_capacity.cols,
terminal_new.page.std_capacity.rows,
0,
);
defer list.deinit();
for (0..count) |_| {
_ = try list.grow();
}
}

View File

@ -1,71 +0,0 @@
//! This benchmark tests the throughput of the terminal escape code parser.
//!
//! To benchmark, this takes an input stream (which is expected to come in
//! as fast as possible), runs it through the parser, and does nothing
//! with the parse result. This bottlenecks and tests the throughput of the
//! parser.
//!
//! Usage:
//!
//! "--f=<path>" - A file to read to parse. If path is "-" then stdin
//! is read. Required.
//!
const std = @import("std");
const ArenaAllocator = std.heap.ArenaAllocator;
const cli = @import("../cli.zig");
const terminal = @import("../terminal/main.zig");
pub fn main() !void {
// Just use a GPA
const GPA = std.heap.GeneralPurposeAllocator(.{});
var gpa = GPA{};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
// Parse our args
var args: Args = args: {
var args: Args = .{};
errdefer args.deinit();
var iter = try cli.args.argsIterator(alloc);
defer iter.deinit();
try cli.args.parse(Args, alloc, &args, &iter);
break :args args;
};
defer args.deinit();
// Read the file for our input
const file = file: {
if (std.mem.eql(u8, args.f, "-"))
break :file std.io.getStdIn();
@panic("file reading not implemented yet");
};
// Read all into memory (TODO: support buffers one day)
const input = try file.reader().readAllAlloc(
alloc,
1024 * 1024 * 1024 * 1024 * 16, // 16 GB
);
defer alloc.free(input);
// Run our parser
var p: terminal.Parser = .{};
for (input) |c| {
const actions = p.next(c);
//std.log.warn("actions={any}", .{actions});
_ = actions;
}
}
const Args = struct {
f: []const u8 = "-",
/// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null,
pub fn deinit(self: *Args) void {
if (self._arena) |arena| arena.deinit();
self.* = undefined;
}
};

View File

@ -1,30 +0,0 @@
#!/usr/bin/env bash
#
# This is a trivial helper script to help run the stream benchmark.
# You probably want to tweak this script depending on what you're
# trying to measure.
# Options:
# - "ascii", uniform random ASCII bytes
# - "utf8", uniform random unicode characters, encoded as utf8
# - "rand", pure random data, will contain many invalid code sequences.
DATA="ascii"
SIZE="25000000"
# Uncomment to test with an active terminal state.
# ARGS=" --terminal"
# Generate the benchmark input ahead of time so it's not included in the time.
./zig-out/bin/bench-stream --mode=gen-$DATA | head -c $SIZE > /tmp/ghostty_bench_data
# Uncomment to instead use the contents of `stream.txt` as input. (Ignores SIZE)
# echo $(cat ./stream.txt) > /tmp/ghostty_bench_data
hyperfine \
--warmup 10 \
-n memcpy \
"./zig-out/bin/bench-stream --mode=noop${ARGS} </tmp/ghostty_bench_data" \
-n scalar \
"./zig-out/bin/bench-stream --mode=scalar${ARGS} </tmp/ghostty_bench_data" \
-n simd \
"./zig-out/bin/bench-stream --mode=simd${ARGS} </tmp/ghostty_bench_data"

View File

@ -1,253 +0,0 @@
//! This benchmark tests the throughput of the VT stream. It has a few
//! modes in order to test different methods of stream processing. It
//! provides a "noop" mode to give us the `memcpy` speed.
//!
//! This will consume all of the available stdin, so you should run it
//! with `head` in a pipe to restrict. For example, to test ASCII input:
//!
//! bench-stream --mode=gen-ascii | head -c 50M | bench-stream --mode=simd
//!
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const cli = @import("../cli.zig");
const terminal = @import("../terminal/main.zig");
const synthetic = @import("../synthetic/main.zig");
const Args = struct {
mode: Mode = .noop,
/// The PRNG seed used by the input generators.
/// -1 uses a random seed (default)
seed: i64 = -1,
/// Process input with a real terminal. This will be MUCH slower than
/// the other modes because it has to maintain terminal state but will
/// help get more realistic numbers.
terminal: Terminal = .none,
@"terminal-rows": usize = 80,
@"terminal-cols": usize = 120,
/// The size for read buffers. Doesn't usually need to be changed. The
/// main point is to make this runtime known so we can avoid compiler
/// optimizations.
@"buffer-size": usize = 4096,
/// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null,
pub fn deinit(self: *Args) void {
if (self._arena) |arena| arena.deinit();
self.* = undefined;
}
const Terminal = enum { none, new };
};
const Mode = enum {
// Do nothing, just read from stdin into a stack-allocated buffer.
// This is used to benchmark our base-case: it gives us our maximum
// throughput on a basic read.
noop,
// These benchmark the throughput of the terminal stream parsing
// with and without SIMD. The "simd" option will use whatever is best
// for the running platform.
//
// Note that these run through the full VT parser but do not apply
// the operations to terminal state, so there is no terminal state
// overhead.
scalar,
simd,
// Generate an infinite stream of random printable ASCII characters.
@"gen-ascii",
// Generate an infinite stream of random printable unicode characters.
@"gen-utf8",
// Generate an infinite stream of arbitrary random bytes.
@"gen-rand",
// Generate an infinite stream of OSC requests. These will be mixed
// with valid and invalid OSC requests by default, but the
// `-valid` and `-invalid`-suffixed variants can be used to get only
// a specific type of OSC request.
@"gen-osc",
@"gen-osc-valid",
@"gen-osc-invalid",
};
pub const std_options: std.Options = .{
.log_level = .debug,
};
pub fn main() !void {
// We want to use the c allocator because it is much faster than GPA.
const alloc = std.heap.c_allocator;
// Parse our args
var args: Args = .{};
defer args.deinit();
{
var iter = try cli.args.argsIterator(alloc);
defer iter.deinit();
try cli.args.parse(Args, alloc, &args, &iter);
}
const reader = std.io.getStdIn().reader();
const writer = std.io.getStdOut().writer();
const buf = try alloc.alloc(u8, args.@"buffer-size");
// Build our RNG
const seed: u64 = if (args.seed >= 0) @bitCast(args.seed) else @truncate(@as(u128, @bitCast(std.time.nanoTimestamp())));
var prng = std.Random.DefaultPrng.init(seed);
const rand = prng.random();
// Handle the modes that do not depend on terminal state first.
switch (args.mode) {
.@"gen-ascii" => {
var gen: synthetic.Bytes = .{
.rand = rand,
.alphabet = synthetic.Bytes.Alphabet.ascii,
};
try generate(writer, gen.generator());
},
.@"gen-utf8" => {
var gen: synthetic.Utf8 = .{
.rand = rand,
};
try generate(writer, gen.generator());
},
.@"gen-rand" => {
var gen: synthetic.Bytes = .{ .rand = rand };
try generate(writer, gen.generator());
},
.@"gen-osc" => {
var gen: synthetic.Osc = .{
.rand = rand,
.p_valid = 0.5,
};
try generate(writer, gen.generator());
},
.@"gen-osc-valid" => {
var gen: synthetic.Osc = .{
.rand = rand,
.p_valid = 1.0,
};
try generate(writer, gen.generator());
},
.@"gen-osc-invalid" => {
var gen: synthetic.Osc = .{
.rand = rand,
.p_valid = 0.0,
};
try generate(writer, gen.generator());
},
.noop => try benchNoop(reader, buf),
// Handle the ones that depend on terminal state next
inline .scalar,
.simd,
=> |tag| switch (args.terminal) {
.new => {
const TerminalStream = terminal.Stream(*TerminalHandler);
var t = try terminal.Terminal.init(alloc, .{
.cols = @intCast(args.@"terminal-cols"),
.rows = @intCast(args.@"terminal-rows"),
});
var handler: TerminalHandler = .{ .t = &t };
var stream: TerminalStream = .{ .handler = &handler };
switch (tag) {
.scalar => try benchScalar(reader, &stream, buf),
.simd => try benchSimd(reader, &stream, buf),
else => @compileError("missing case"),
}
},
.none => {
var stream: terminal.Stream(NoopHandler) = .{ .handler = .{} };
switch (tag) {
.scalar => try benchScalar(reader, &stream, buf),
.simd => try benchSimd(reader, &stream, buf),
else => @compileError("missing case"),
}
},
},
}
}
fn generate(
writer: anytype,
gen: synthetic.Generator,
) !void {
var buf: [1024]u8 = undefined;
while (true) {
const data = try gen.next(&buf);
writer.writeAll(data) catch |err| switch (err) {
error.BrokenPipe => return, // stdout closed
else => return err,
};
}
}
noinline fn benchNoop(reader: anytype, buf: []u8) !void {
var total: usize = 0;
while (true) {
const n = try reader.readAll(buf);
if (n == 0) break;
total += n;
}
std.log.info("total bytes len={}", .{total});
}
noinline fn benchScalar(
reader: anytype,
stream: anytype,
buf: []u8,
) !void {
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
// Using stream.next directly with a for loop applies a naive
// scalar approach.
for (buf[0..n]) |c| try stream.next(c);
}
}
noinline fn benchSimd(
reader: anytype,
stream: anytype,
buf: []u8,
) !void {
while (true) {
const n = try reader.read(buf);
if (n == 0) break;
try stream.nextSlice(buf[0..n]);
}
}
const NoopHandler = struct {
pub fn print(self: NoopHandler, cp: u21) !void {
_ = self;
_ = cp;
}
};
const TerminalHandler = struct {
t: *terminal.Terminal,
pub fn print(self: *TerminalHandler, cp: u21) !void {
try self.t.print(cp);
}
};

166
src/benchmark/Benchmark.zig Normal file
View File

@ -0,0 +1,166 @@
//! A single benchmark case.
const Benchmark = @This();
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const macos = @import("macos");
const build_config = @import("../build_config.zig");
ptr: *anyopaque,
vtable: VTable,
/// Create a new benchmark from a pointer and a vtable.
///
/// This usually is only called by benchmark implementations, not
/// benchmark users.
pub fn init(
pointer: anytype,
vtable: VTable,
) Benchmark {
const Ptr = @TypeOf(pointer);
assert(@typeInfo(Ptr) == .pointer); // Must be a pointer
assert(@typeInfo(Ptr).pointer.size == .one); // Must be a single-item pointer
assert(@typeInfo(@typeInfo(Ptr).pointer.child) == .@"struct"); // Must point to a struct
return .{ .ptr = pointer, .vtable = vtable };
}
/// Run the benchmark.
pub fn run(
self: Benchmark,
mode: RunMode,
) Error!RunResult {
// Run our setup function if it exists. We do this first because
// we don't want this part of our benchmark and we want to fail fast.
if (self.vtable.setupFn) |func| try func(self.ptr);
defer if (self.vtable.teardownFn) |func| func(self.ptr);
// Our result accumulator. This will be returned at the end of the run.
var result: RunResult = .{};
// If we're on macOS, we setup signposts so its easier to find
// the results in Instruments. There's a lot of nasty comptime stuff
// here but its just to ensure this does nothing on other platforms.
const signpost_name = "ghostty";
const signpost: if (builtin.target.os.tag.isDarwin()) struct {
log: *macos.os.Log,
id: macos.os.signpost.Id,
} else void = if (builtin.target.os.tag.isDarwin()) darwin: {
macos.os.signpost.init();
const log = macos.os.Log.create(
build_config.bundle_id,
macos.os.signpost.Category.points_of_interest,
);
const id = macos.os.signpost.Id.forPointer(log, self.ptr);
macos.os.signpost.intervalBegin(log, id, signpost_name);
break :darwin .{ .log = log, .id = id };
} else {};
defer if (comptime builtin.target.os.tag.isDarwin()) {
macos.os.signpost.intervalEnd(
signpost.log,
signpost.id,
signpost_name,
);
signpost.log.release();
};
const start = std.time.Instant.now() catch return error.BenchmarkFailed;
while (true) {
// Run our step function. If it fails, we return the error.
try self.vtable.stepFn(self.ptr);
result.iterations += 1;
// Get our current monotonic time and check our exit conditions.
const now = std.time.Instant.now() catch return error.BenchmarkFailed;
const exit = switch (mode) {
.once => true,
.duration => |ns| now.since(start) >= ns,
};
if (exit) {
result.duration = now.since(start);
return result;
}
}
// We exit within the loop body.
unreachable;
}
/// The type of benchmark run. This is used to determine how the benchmark
/// is executed.
pub const RunMode = union(enum) {
/// Run the benchmark exactly once.
once,
/// Run the benchmark for a fixed duration in nanoseconds. This
/// will not interrupt a running step so if the granularity of the
/// duration is too low, benchmark results may be inaccurate.
duration: u64,
};
/// The result of a benchmark run.
pub const RunResult = struct {
/// The total iterations that step was executed. For "once" run
/// modes this will always be 1.
iterations: u32 = 0,
/// The total time taken for the run. For "duration" run modes
/// this will be relatively close to the requested duration.
/// The units are nanoseconds.
duration: u64 = 0,
};
/// The possible errors that can occur during various stages of the
/// benchmark. Right now its just "failure" which ends the benchmark.
pub const Error = error{BenchmarkFailed};
/// The vtable that must be provided to invoke the real implementation.
pub const VTable = struct {
/// A single step to execute the benchmark. This should do the work
/// that is under test. This may be called multiple times if we're
/// testing throughput.
stepFn: *const fn (ptr: *anyopaque) Error!void,
/// Setup and teardown functions. These are called once before
/// the first step and once after the last step. They are not part
/// of the benchmark results (unless you're benchmarking the full
/// binary).
setupFn: ?*const fn (ptr: *anyopaque) Error!void = null,
teardownFn: ?*const fn (ptr: *anyopaque) void = null,
};
test Benchmark {
const testing = std.testing;
const Simple = struct {
const Self = @This();
setup_i: usize = 0,
step_i: usize = 0,
pub fn benchmark(self: *Self) Benchmark {
return .init(self, .{
.stepFn = step,
.setupFn = setup,
});
}
fn setup(ptr: *anyopaque) Error!void {
const self: *Self = @ptrCast(@alignCast(ptr));
self.setup_i += 1;
}
fn step(ptr: *anyopaque) Error!void {
const self: *Self = @ptrCast(@alignCast(ptr));
self.step_i += 1;
}
};
var s: Simple = .{};
const b = s.benchmark();
const result = try b.run(.once);
try testing.expectEqual(1, s.setup_i);
try testing.expectEqual(1, s.step_i);
try testing.expectEqual(1, result.iterations);
try testing.expect(result.duration > 0);
}

34
src/benchmark/CApi.zig Normal file
View File

@ -0,0 +1,34 @@
const std = @import("std");
const cli = @import("cli.zig");
const state = &@import("../global.zig").state;
const log = std.log.scoped(.benchmark);
/// Run the Ghostty benchmark CLI with the given action and arguments.
export fn ghostty_benchmark_cli(
action_name_: [*:0]const u8,
args: [*:0]const u8,
) bool {
const action_name = std.mem.sliceTo(action_name_, 0);
const action: cli.Action = std.meta.stringToEnum(
cli.Action,
action_name,
) orelse {
log.warn("unknown action={s}", .{action_name});
return false;
};
cli.mainAction(
state.alloc,
action,
.{ .string = std.mem.sliceTo(args, 0) },
) catch |err| {
log.warn("failed to run action={s} err={}", .{
@tagName(action),
err,
});
return false;
};
return true;
}

View File

@ -0,0 +1,204 @@
//! This benchmark tests the throughput of codepoint width calculation.
//! This is a common operation in terminal character printing and the
//! motivating factor to write this benchmark was discovering that our
//! codepoint width function was 30% of the runtime of every character
//! print.
const CodepointWidth = @This();
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig");
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const simd = @import("../simd/main.zig");
const table = @import("../unicode/main.zig").table;
const log = std.log.scoped(.@"terminal-stream-bench");
opts: Options,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
pub const Options = struct {
/// The type of codepoint width calculation to use.
mode: Mode = .noop,
/// The data to read as a filepath. If this is "-" then
/// we will read stdin. If this is unset, then we will
/// do nothing (benchmark is a noop). It'd be more unixy to
/// use stdin by default but I find that a hanging CLI command
/// with no interaction is a bit annoying.
data: ?[]const u8 = null,
};
pub const Mode = enum {
/// The baseline mode copies the data from the fd into a buffer. This
/// is used to show the minimal overhead of reading the fd into memory
/// and establishes a baseline for the other modes.
noop,
/// libc wcwidth
wcwidth,
/// Our SIMD implementation.
simd,
/// Test our lookup table implementation.
table,
};
/// Create a new terminal stream handler for the given arguments.
pub fn create(
alloc: Allocator,
opts: Options,
) !*CodepointWidth {
const ptr = try alloc.create(CodepointWidth);
errdefer alloc.destroy(ptr);
ptr.* = .{ .opts = opts };
return ptr;
}
pub fn destroy(self: *CodepointWidth, alloc: Allocator) void {
alloc.destroy(self);
}
pub fn benchmark(self: *CodepointWidth) Benchmark {
return .init(self, .{
.stepFn = switch (self.opts.mode) {
.noop => stepNoop,
.wcwidth => stepWcwidth,
.table => stepTable,
.simd => stepSimd,
},
.setupFn = setup,
.teardownFn = teardown,
});
}
fn setup(ptr: *anyopaque) Benchmark.Error!void {
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
// Open our data file to prepare for reading. We can do more
// validation here eventually.
assert(self.data_f == null);
self.data_f = options.dataFile(self.opts.data) catch |err| {
log.warn("error opening data file err={}", .{err});
return error.BenchmarkFailed;
};
}
fn teardown(ptr: *anyopaque) void {
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
self.data_f = null;
}
}
fn stepNoop(ptr: *anyopaque) Benchmark.Error!void {
_ = ptr;
}
extern "c" fn wcwidth(c: u32) c_int;
fn stepWcwidth(ptr: *anyopaque) Benchmark.Error!void {
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return;
var r = std.io.bufferedReader(f.reader());
var d: UTF8Decoder = .{};
var buf: [4096]u8 = undefined;
while (true) {
const n = r.read(&buf) catch |err| {
log.warn("error reading data file err={}", .{err});
return error.BenchmarkFailed;
};
if (n == 0) break; // EOF reached
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp| {
const width = wcwidth(cp);
// Write the width to the buffer to avoid it being compiled
// away
buf[0] = @intCast(width);
}
}
}
}
fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return;
var r = std.io.bufferedReader(f.reader());
var d: UTF8Decoder = .{};
var buf: [4096]u8 = undefined;
while (true) {
const n = r.read(&buf) catch |err| {
log.warn("error reading data file err={}", .{err});
return error.BenchmarkFailed;
};
if (n == 0) break; // EOF reached
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp| {
// This is the same trick we do in terminal.zig so we
// keep it here.
const width = if (cp <= 0xFF)
1
else
table.get(@intCast(cp)).width;
// Write the width to the buffer to avoid it being compiled
// away
buf[0] = @intCast(width);
}
}
}
}
fn stepSimd(ptr: *anyopaque) Benchmark.Error!void {
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return;
var r = std.io.bufferedReader(f.reader());
var d: UTF8Decoder = .{};
var buf: [4096]u8 = undefined;
while (true) {
const n = r.read(&buf) catch |err| {
log.warn("error reading data file err={}", .{err});
return error.BenchmarkFailed;
};
if (n == 0) break; // EOF reached
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp| {
const width = simd.codepointWidth(cp);
// Write the width to the buffer to avoid it being compiled
// away
buf[0] = @intCast(width);
}
}
}
}
test CodepointWidth {
const testing = std.testing;
const alloc = testing.allocator;
const impl: *CodepointWidth = try .create(alloc, .{});
defer impl.destroy(alloc);
const bench = impl.benchmark();
_ = try bench.run(.once);
}

View File

@ -0,0 +1,146 @@
//! This benchmark tests the throughput of grapheme break calculation.
//! This is a common operation in terminal character printing for terminals
//! that support grapheme clustering.
const GraphemeBreak = @This();
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig");
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const unicode = @import("../unicode/main.zig");
const log = std.log.scoped(.@"terminal-stream-bench");
opts: Options,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
pub const Options = struct {
/// The type of codepoint width calculation to use.
mode: Mode = .table,
/// The data to read as a filepath. If this is "-" then
/// we will read stdin. If this is unset, then we will
/// do nothing (benchmark is a noop). It'd be more unixy to
/// use stdin by default but I find that a hanging CLI command
/// with no interaction is a bit annoying.
data: ?[]const u8 = null,
};
pub const Mode = enum {
/// The baseline mode copies the data from the fd into a buffer. This
/// is used to show the minimal overhead of reading the fd into memory
/// and establishes a baseline for the other modes.
noop,
/// Ghostty's table-based approach.
table,
};
/// Create a new terminal stream handler for the given arguments.
pub fn create(
alloc: Allocator,
opts: Options,
) !*GraphemeBreak {
const ptr = try alloc.create(GraphemeBreak);
errdefer alloc.destroy(ptr);
ptr.* = .{ .opts = opts };
return ptr;
}
pub fn destroy(self: *GraphemeBreak, alloc: Allocator) void {
alloc.destroy(self);
}
pub fn benchmark(self: *GraphemeBreak) Benchmark {
return .init(self, .{
.stepFn = switch (self.opts.mode) {
.noop => stepNoop,
.table => stepTable,
},
.setupFn = setup,
.teardownFn = teardown,
});
}
fn setup(ptr: *anyopaque) Benchmark.Error!void {
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
// Open our data file to prepare for reading. We can do more
// validation here eventually.
assert(self.data_f == null);
self.data_f = options.dataFile(self.opts.data) catch |err| {
log.warn("error opening data file err={}", .{err});
return error.BenchmarkFailed;
};
}
fn teardown(ptr: *anyopaque) void {
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
self.data_f = null;
}
}
fn stepNoop(ptr: *anyopaque) Benchmark.Error!void {
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return;
var r = std.io.bufferedReader(f.reader());
var d: UTF8Decoder = .{};
var buf: [4096]u8 = undefined;
while (true) {
const n = r.read(&buf) catch |err| {
log.warn("error reading data file err={}", .{err});
return error.BenchmarkFailed;
};
if (n == 0) break; // EOF reached
for (buf[0..n]) |c| {
_ = d.next(c);
}
}
}
fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return;
var r = std.io.bufferedReader(f.reader());
var d: UTF8Decoder = .{};
var state: unicode.GraphemeBreakState = .{};
var cp1: u21 = 0;
var buf: [4096]u8 = undefined;
while (true) {
const n = r.read(&buf) catch |err| {
log.warn("error reading data file err={}", .{err});
return error.BenchmarkFailed;
};
if (n == 0) break; // EOF reached
for (buf[0..n]) |c| {
const cp_, const consumed = d.next(c);
assert(consumed);
if (cp_) |cp2| {
const v = unicode.graphemeBreak(cp1, @intCast(cp2), &state);
buf[0] = @intCast(@intFromBool(v));
cp1 = cp2;
}
}
}
}
test GraphemeBreak {
const testing = std.testing;
const alloc = testing.allocator;
const impl: *GraphemeBreak = try .create(alloc, .{});
defer impl.destroy(alloc);
const bench = impl.benchmark();
_ = try bench.run(.once);
}

View File

@ -0,0 +1,106 @@
//! This benchmark tests the throughput of the terminal escape code parser.
const TerminalParser = @This();
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const terminalpkg = @import("../terminal/main.zig");
const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig");
const log = std.log.scoped(.@"terminal-stream-bench");
opts: Options,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
pub const Options = struct {
/// The data to read as a filepath. If this is "-" then
/// we will read stdin. If this is unset, then we will
/// do nothing (benchmark is a noop). It'd be more unixy to
/// use stdin by default but I find that a hanging CLI command
/// with no interaction is a bit annoying.
data: ?[]const u8 = null,
};
pub fn create(
alloc: Allocator,
opts: Options,
) !*TerminalParser {
const ptr = try alloc.create(TerminalParser);
errdefer alloc.destroy(ptr);
ptr.* = .{ .opts = opts };
return ptr;
}
pub fn destroy(self: *TerminalParser, alloc: Allocator) void {
alloc.destroy(self);
}
pub fn benchmark(self: *TerminalParser) Benchmark {
return .init(self, .{
.stepFn = step,
.setupFn = setup,
.teardownFn = teardown,
});
}
fn setup(ptr: *anyopaque) Benchmark.Error!void {
const self: *TerminalParser = @ptrCast(@alignCast(ptr));
// Open our data file to prepare for reading. We can do more
// validation here eventually.
assert(self.data_f == null);
self.data_f = options.dataFile(self.opts.data) catch |err| {
log.warn("error opening data file err={}", .{err});
return error.BenchmarkFailed;
};
}
fn teardown(ptr: *anyopaque) void {
const self: *TerminalParser = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
self.data_f = null;
}
}
fn step(ptr: *anyopaque) Benchmark.Error!void {
const self: *TerminalParser = @ptrCast(@alignCast(ptr));
// Get our buffered reader so we're not predominantly
// waiting on file IO. It'd be better to move this fully into
// memory. If we're IO bound though that should show up on
// the benchmark results and... I know writing this that we
// aren't currently IO bound.
const f = self.data_f orelse return;
var r = std.io.bufferedReader(f.reader());
var p: terminalpkg.Parser = .{};
var buf: [4096]u8 = undefined;
while (true) {
const n = r.read(&buf) catch |err| {
log.warn("error reading data file err={}", .{err});
return error.BenchmarkFailed;
};
if (n == 0) break; // EOF reached
for (buf[0..n]) |c| {
const actions = p.next(c);
//std.log.warn("actions={any}", .{actions});
_ = actions;
}
}
}
test TerminalParser {
const testing = std.testing;
const alloc = testing.allocator;
const impl: *TerminalParser = try .create(alloc, .{});
defer impl.destroy(alloc);
const bench = impl.benchmark();
_ = try bench.run(.once);
}

View File

@ -0,0 +1,153 @@
//! This benchmark tests the performance of the terminal stream
//! handler from input to terminal state update. This is useful to
//! test general throughput of VT parsing and handling.
//!
//! Note that the handler used for this benchmark isn't the full
//! terminal handler, since that requires a significant amount of
//! state. This is a simplified version that only handles specific
//! terminal operations like printing characters. We should expand
//! this to include more operations to improve the accuracy of the
//! benchmark.
//!
//! It is a fairly broad benchmark that can be used to determine
//! if we need to optimize something more specific (e.g. the parser).
const TerminalStream = @This();
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const terminalpkg = @import("../terminal/main.zig");
const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig");
const Terminal = terminalpkg.Terminal;
const Stream = terminalpkg.Stream(*Handler);
const log = std.log.scoped(.@"terminal-stream-bench");
opts: Options,
terminal: Terminal,
handler: Handler,
stream: Stream,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
pub const Options = struct {
/// The size of the terminal. This affects benchmarking when
/// dealing with soft line wrapping and the memory impact
/// of page sizes.
@"terminal-rows": u16 = 80,
@"terminal-cols": u16 = 120,
/// The data to read as a filepath. If this is "-" then
/// we will read stdin. If this is unset, then we will
/// do nothing (benchmark is a noop). It'd be more unixy to
/// use stdin by default but I find that a hanging CLI command
/// with no interaction is a bit annoying.
data: ?[]const u8 = null,
};
/// Create a new terminal stream handler for the given arguments.
pub fn create(
alloc: Allocator,
opts: Options,
) !*TerminalStream {
const ptr = try alloc.create(TerminalStream);
errdefer alloc.destroy(ptr);
ptr.* = .{
.opts = opts,
.terminal = try .init(alloc, .{
.rows = opts.@"terminal-rows",
.cols = opts.@"terminal-cols",
}),
.handler = .{ .t = &ptr.terminal },
.stream = .{ .handler = &ptr.handler },
};
return ptr;
}
pub fn destroy(self: *TerminalStream, alloc: Allocator) void {
self.terminal.deinit(alloc);
alloc.destroy(self);
}
pub fn benchmark(self: *TerminalStream) Benchmark {
return .init(self, .{
.stepFn = step,
.setupFn = setup,
.teardownFn = teardown,
});
}
fn setup(ptr: *anyopaque) Benchmark.Error!void {
const self: *TerminalStream = @ptrCast(@alignCast(ptr));
// Always reset our terminal state
self.terminal.fullReset();
// Open our data file to prepare for reading. We can do more
// validation here eventually.
assert(self.data_f == null);
self.data_f = options.dataFile(self.opts.data) catch |err| {
log.warn("error opening data file err={}", .{err});
return error.BenchmarkFailed;
};
}
fn teardown(ptr: *anyopaque) void {
const self: *TerminalStream = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
self.data_f = null;
}
}
fn step(ptr: *anyopaque) Benchmark.Error!void {
const self: *TerminalStream = @ptrCast(@alignCast(ptr));
// Get our buffered reader so we're not predominantly
// waiting on file IO. It'd be better to move this fully into
// memory. If we're IO bound though that should show up on
// the benchmark results and... I know writing this that we
// aren't currently IO bound.
const f = self.data_f orelse return;
var r = std.io.bufferedReader(f.reader());
var buf: [4096]u8 = undefined;
while (true) {
const n = r.read(&buf) catch |err| {
log.warn("error reading data file err={}", .{err});
return error.BenchmarkFailed;
};
if (n == 0) break; // EOF reached
const chunk = buf[0..n];
self.stream.nextSlice(chunk) catch |err| {
log.warn("error processing data file chunk err={}", .{err});
return error.BenchmarkFailed;
};
}
}
/// Implements the handler interface for the terminal.Stream.
/// We should expand this to include more operations to make
/// our benchmark more realistic.
const Handler = struct {
t: *Terminal,
pub fn print(self: *Handler, cp: u21) !void {
try self.t.print(cp);
}
};
test TerminalStream {
const testing = std.testing;
const alloc = testing.allocator;
const impl: *TerminalStream = try .create(alloc, .{});
defer impl.destroy(alloc);
const bench = impl.benchmark();
_ = try bench.run(.once);
}

94
src/benchmark/cli.zig Normal file
View File

@ -0,0 +1,94 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const cli = @import("../cli.zig");
/// The available actions for the CLI. This is the list of available
/// benchmarks. View docs for each individual one in the predictably
/// named files.
pub const Action = enum {
@"codepoint-width",
@"grapheme-break",
@"terminal-parser",
@"terminal-stream",
/// Returns the struct associated with the action. The struct
/// should have a few decls:
///
/// - `const Options`: The CLI options for the action.
/// - `fn create`: Create a new instance of the action from options.
/// - `fn benchmark`: Returns a `Benchmark` instance for the action.
///
/// See TerminalStream for an example.
pub fn Struct(comptime action: Action) type {
return switch (action) {
.@"terminal-stream" => @import("TerminalStream.zig"),
.@"codepoint-width" => @import("CodepointWidth.zig"),
.@"grapheme-break" => @import("GraphemeBreak.zig"),
.@"terminal-parser" => @import("TerminalParser.zig"),
};
}
};
/// An entrypoint for the benchmark CLI.
pub fn main() !void {
const alloc = std.heap.c_allocator;
const action_ = try cli.action.detectArgs(Action, alloc);
const action = action_ orelse return error.NoAction;
try mainAction(alloc, action, .cli);
}
/// Arguments that can be passed to the benchmark.
pub const Args = union(enum) {
/// The arguments passed to the CLI via argc/argv.
cli,
/// Simple string arguments, parsed via std.process.ArgIteratorGeneral.
string: []const u8,
};
pub fn mainAction(
alloc: Allocator,
action: Action,
args: Args,
) !void {
switch (action) {
inline else => |comptime_action| {
const BenchmarkImpl = Action.Struct(comptime_action);
try mainActionImpl(BenchmarkImpl, alloc, args);
},
}
}
fn mainActionImpl(
comptime BenchmarkImpl: type,
alloc: Allocator,
args: Args,
) !void {
// First, parse our CLI options.
const Options = BenchmarkImpl.Options;
var opts: Options = .{};
defer if (@hasDecl(Options, "deinit")) opts.deinit();
switch (args) {
.cli => {
var iter = try cli.args.argsIterator(alloc);
defer iter.deinit();
try cli.args.parse(Options, alloc, &opts, &iter);
},
.string => |str| {
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
str,
);
defer iter.deinit();
try cli.args.parse(Options, alloc, &opts, &iter);
},
}
// Create our implementation
const impl = try BenchmarkImpl.create(alloc, opts);
defer impl.destroy(alloc);
// Initialize our benchmark
const b = impl.benchmark();
_ = try b.run(.once);
}

11
src/benchmark/main.zig Normal file
View File

@ -0,0 +1,11 @@
pub const cli = @import("cli.zig");
pub const Benchmark = @import("Benchmark.zig");
pub const CApi = @import("CApi.zig");
pub const TerminalStream = @import("TerminalStream.zig");
pub const CodepointWidth = @import("CodepointWidth.zig");
pub const GraphemeBreak = @import("GraphemeBreak.zig");
pub const TerminalParser = @import("TerminalParser.zig");
test {
@import("std").testing.refAllDecls(@This());
}

20
src/benchmark/options.zig Normal file
View File

@ -0,0 +1,20 @@
//! This file contains helpers for CLI options.
const std = @import("std");
/// Returns the data file for the given path in a way that is consistent
/// across our CLI. If the path is not set then no file is returned.
/// If the path is "-", then we will return stdin. If the path is
/// a file then we will open and return the handle.
pub fn dataFile(path_: ?[]const u8) !?std.fs.File {
const path = path_ orelse return null;
// Stdin
if (std.mem.eql(u8, path, "-")) return std.io.getStdIn();
// Normal file
const file = try std.fs.cwd().openFile(path, .{});
errdefer file.close();
return file;
}

View File

@ -528,11 +528,6 @@ pub const ExeEntrypoint = enum {
webgen_config, webgen_config,
webgen_actions, webgen_actions,
webgen_commands, webgen_commands,
bench_parser,
bench_stream,
bench_codepoint_width,
bench_grapheme_break,
bench_page_init,
}; };
/// The release channel for the build. /// The release channel for the build.

View File

@ -14,52 +14,37 @@ pub fn init(
var steps = std.ArrayList(*std.Build.Step.Compile).init(b.allocator); var steps = std.ArrayList(*std.Build.Step.Compile).init(b.allocator);
errdefer steps.deinit(); errdefer steps.deinit();
// Open the directory ./src/bench // Our synthetic data generator
const c_dir_path = b.pathFromRoot("src/bench"); {
var c_dir = try std.fs.cwd().openDir(c_dir_path, .{ .iterate = true }); const exe = b.addExecutable(.{
defer c_dir.close(); .name = "ghostty-gen",
// Go through and add each as a step
var c_dir_it = c_dir.iterate();
while (try c_dir_it.next()) |entry| {
// Get the index of the last '.' so we can strip the extension.
const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue;
if (index == 0) continue;
// If it doesn't end in 'zig' then ignore
if (!std.mem.eql(u8, entry.name[index + 1 ..], "zig")) continue;
// Name of the conformance app and full path to the entrypoint.
const name = entry.name[0..index];
// Executable builder.
const bin_name = try std.fmt.allocPrint(b.allocator, "bench-{s}", .{name});
const c_exe = b.addExecutable(.{
.name = bin_name,
.root_module = b.createModule(.{ .root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"), .root_source_file = b.path("src/main_gen.zig"),
.target = deps.config.target, .target = deps.config.target,
// We always want our datagen to be fast because it
// takes awhile to run.
.optimize = .ReleaseFast,
}),
});
exe.linkLibC();
_ = try deps.add(exe);
try steps.append(exe);
}
// Our benchmarking application.
{
const exe = b.addExecutable(.{
.name = "ghostty-bench",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main_bench.zig"),
.target = deps.config.target,
// We always want our benchmarks to be in release mode. // We always want our benchmarks to be in release mode.
.optimize = .ReleaseFast, .optimize = .ReleaseFast,
}), }),
}); });
c_exe.linkLibC(); exe.linkLibC();
_ = try deps.add(exe);
// Update our entrypoint try steps.append(exe);
var enum_name: [64]u8 = undefined;
@memcpy(enum_name[0..name.len], name);
std.mem.replaceScalar(u8, enum_name[0..name.len], '-', '_');
var buf: [64]u8 = undefined;
const new_deps = try deps.changeEntrypoint(b, std.meta.stringToEnum(
Config.ExeEntrypoint,
try std.fmt.bufPrint(&buf, "bench_{s}", .{enum_name[0..name.len]}),
).?);
_ = try new_deps.add(c_exe);
try steps.append(c_exe);
} }
return .{ .steps = steps.items }; return .{ .steps = steps.items };

View File

@ -1,6 +1,7 @@
const GhosttyLib = @This(); const GhosttyLib = @This();
const std = @import("std"); const std = @import("std");
const RunStep = std.Build.Step.Run;
const Config = @import("Config.zig"); const Config = @import("Config.zig");
const SharedDeps = @import("SharedDeps.zig"); const SharedDeps = @import("SharedDeps.zig");
const LibtoolStep = @import("LibtoolStep.zig"); const LibtoolStep = @import("LibtoolStep.zig");
@ -11,6 +12,7 @@ step: *std.Build.Step,
/// The final static library file /// The final static library file
output: std.Build.LazyPath, output: std.Build.LazyPath,
dsym: ?std.Build.LazyPath,
pub fn initStatic( pub fn initStatic(
b: *std.Build, b: *std.Build,
@ -18,9 +20,14 @@ pub fn initStatic(
) !GhosttyLib { ) !GhosttyLib {
const lib = b.addStaticLibrary(.{ const lib = b.addStaticLibrary(.{
.name = "ghostty", .name = "ghostty",
.root_source_file = b.path("src/main_c.zig"), .root_module = b.createModule(.{
.target = deps.config.target, .root_source_file = b.path("src/main_c.zig"),
.optimize = deps.config.optimize, .target = deps.config.target,
.optimize = deps.config.optimize,
.strip = deps.config.strip,
.omit_frame_pointer = deps.config.strip,
.unwind_tables = if (deps.config.strip) .none else .sync,
}),
}); });
lib.linkLibC(); lib.linkLibC();
@ -37,6 +44,7 @@ pub fn initStatic(
if (!deps.config.target.result.os.tag.isDarwin()) return .{ if (!deps.config.target.result.os.tag.isDarwin()) return .{
.step = &lib.step, .step = &lib.step,
.output = lib.getEmittedBin(), .output = lib.getEmittedBin(),
.dsym = null,
}; };
// Create a static lib that contains all our dependencies. // Create a static lib that contains all our dependencies.
@ -50,6 +58,9 @@ pub fn initStatic(
return .{ return .{
.step = libtool.step, .step = libtool.step,
.output = libtool.output, .output = libtool.output,
// Static libraries cannot have dSYMs because they aren't linked.
.dsym = null,
}; };
} }
@ -59,16 +70,35 @@ pub fn initShared(
) !GhosttyLib { ) !GhosttyLib {
const lib = b.addSharedLibrary(.{ const lib = b.addSharedLibrary(.{
.name = "ghostty", .name = "ghostty",
.root_source_file = b.path("src/main_c.zig"), .root_module = b.createModule(.{
.target = deps.config.target, .root_source_file = b.path("src/main_c.zig"),
.optimize = deps.config.optimize, .target = deps.config.target,
.strip = deps.config.strip, .optimize = deps.config.optimize,
.strip = deps.config.strip,
.omit_frame_pointer = deps.config.strip,
.unwind_tables = if (deps.config.strip) .none else .sync,
}),
}); });
_ = try deps.add(lib); _ = try deps.add(lib);
// Get our debug symbols
const dsymutil: ?std.Build.LazyPath = dsymutil: {
if (!deps.config.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.dSYM");
break :dsymutil output;
};
return .{ return .{
.step = &lib.step, .step = &lib.step,
.output = lib.getEmittedBin(), .output = lib.getEmittedBin(),
.dsym = dsymutil,
}; };
} }
@ -95,6 +125,10 @@ pub fn initMacOSUniversal(
return .{ return .{
.step = universal.step, .step = universal.step,
.output = universal.output, .output = universal.output,
// You can't run dsymutil on a universal binary, you have to
// do it on the individual binaries.
.dsym = null,
}; };
} }

View File

@ -64,20 +64,24 @@ pub fn init(
.{ .{
.library = macos_universal.output, .library = macos_universal.output,
.headers = b.path("include"), .headers = b.path("include"),
.dsym = macos_universal.dsym,
}, },
.{ .{
.library = ios.output, .library = ios.output,
.headers = b.path("include"), .headers = b.path("include"),
.dsym = ios.dsym,
}, },
.{ .{
.library = ios_sim.output, .library = ios_sim.output,
.headers = b.path("include"), .headers = b.path("include"),
.dsym = ios_sim.dsym,
}, },
}, },
.native => &.{.{ .native => &.{.{
.library = macos_native.output, .library = macos_native.output,
.headers = b.path("include"), .headers = b.path("include"),
.dsym = macos_native.dsym,
}}, }},
}, },
}); });

View File

@ -12,6 +12,7 @@ const XCFramework = @import("GhosttyXCFramework.zig");
build: *std.Build.Step.Run, build: *std.Build.Step.Run,
open: *std.Build.Step.Run, open: *std.Build.Step.Run,
copy: *std.Build.Step.Run, copy: *std.Build.Step.Run,
xctest: *std.Build.Step.Run,
pub const Deps = struct { pub const Deps = struct {
xcframework: *const XCFramework, xcframework: *const XCFramework,
@ -33,6 +34,21 @@ pub fn init(
=> "Release", => "Release",
}; };
const xc_arch: ?[]const u8 = switch (deps.xcframework.target) {
// Universal is our default target, so we don't have to
// add anything.
.universal => null,
// Native we need to override the architecture in the Xcode
// project with the -arch flag.
.native => switch (builtin.cpu.arch) {
.aarch64 => "arm64",
.x86_64 => "x86_64",
else => @panic("unsupported macOS arch"),
},
};
const env = try std.process.getEnvMap(b.allocator);
const app_path = b.fmt("macos/build/{s}/Ghostty.app", .{xc_config}); const app_path = b.fmt("macos/build/{s}/Ghostty.app", .{xc_config});
// Our step to build the Ghostty macOS app. // Our step to build the Ghostty macOS app.
@ -41,12 +57,13 @@ pub fn init(
// we create a new empty environment. // we create a new empty environment.
const env_map = try b.allocator.create(std.process.EnvMap); const env_map = try b.allocator.create(std.process.EnvMap);
env_map.* = .init(b.allocator); env_map.* = .init(b.allocator);
if (env.get("PATH")) |v| try env_map.put("PATH", v);
const build = RunStep.create(b, "xcodebuild"); const step = RunStep.create(b, "xcodebuild");
build.has_side_effects = true; step.has_side_effects = true;
build.cwd = b.path("macos"); step.cwd = b.path("macos");
build.env_map = env_map; step.env_map = env_map;
build.addArgs(&.{ step.addArgs(&.{
"xcodebuild", "xcodebuild",
"-target", "-target",
"Ghostty", "Ghostty",
@ -54,36 +71,55 @@ pub fn init(
xc_config, xc_config,
}); });
switch (deps.xcframework.target) { // If we have a specific architecture, we need to pass it
// Universal is our default target, so we don't have to // to xcodebuild.
// add anything. if (xc_arch) |arch| step.addArgs(&.{ "-arch", arch });
.universal => {},
// Native we need to override the architecture in the Xcode
// project with the -arch flag.
.native => build.addArgs(&.{
"-arch",
switch (builtin.cpu.arch) {
.aarch64 => "arm64",
.x86_64 => "x86_64",
else => @panic("unsupported macOS arch"),
},
}),
}
// We need the xcframework // We need the xcframework
deps.xcframework.addStepDependencies(&build.step); deps.xcframework.addStepDependencies(&step.step);
// We also need all these resources because the xcode project // We also need all these resources because the xcode project
// references them via symlinks. // references them via symlinks.
deps.resources.addStepDependencies(&build.step); deps.resources.addStepDependencies(&step.step);
deps.i18n.addStepDependencies(&build.step); deps.i18n.addStepDependencies(&step.step);
deps.docs.installDummy(&build.step); deps.docs.installDummy(&step.step);
// Expect success // Expect success
build.expectExitCode(0); step.expectExitCode(0);
break :build build; break :build step;
};
const xctest = xctest: {
const env_map = try b.allocator.create(std.process.EnvMap);
env_map.* = .init(b.allocator);
if (env.get("PATH")) |v| try env_map.put("PATH", v);
const step = RunStep.create(b, "xcodebuild test");
step.has_side_effects = true;
step.cwd = b.path("macos");
step.env_map = env_map;
step.addArgs(&.{
"xcodebuild",
"test",
"-scheme",
"Ghostty",
});
if (xc_arch) |arch| step.addArgs(&.{ "-arch", arch });
// We need the xcframework
deps.xcframework.addStepDependencies(&step.step);
// We also need all these resources because the xcode project
// references them via symlinks.
deps.resources.addStepDependencies(&step.step);
deps.i18n.addStepDependencies(&step.step);
deps.docs.installDummy(&step.step);
// Expect success
step.expectExitCode(0);
break :xctest step;
}; };
// Our step to open the resulting Ghostty app. // Our step to open the resulting Ghostty app.
@ -143,6 +179,7 @@ pub fn init(
.build = build, .build = build,
.open = open, .open = open,
.copy = copy, .copy = copy,
.xctest = xctest,
}; };
} }
@ -155,3 +192,10 @@ pub fn installXcframework(self: *const Ghostty) void {
const b = self.build.step.owner; const b = self.build.step.owner;
b.getInstallStep().dependOn(&self.build.step); b.getInstallStep().dependOn(&self.build.step);
} }
pub fn addTestStepDependencies(
self: *const Ghostty,
other_step: *std.Build.Step,
) void {
other_step.dependOn(&self.xctest.step);
}

View File

@ -139,7 +139,7 @@ pub fn add(
if (b.lazyDependency("harfbuzz", .{ if (b.lazyDependency("harfbuzz", .{
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.@"enable-freetype" = true, .@"enable-freetype" = self.config.font_backend.hasFreetype(),
.@"enable-coretext" = self.config.font_backend.hasCoretext(), .@"enable-coretext" = self.config.font_backend.hasCoretext(),
})) |harfbuzz_dep| { })) |harfbuzz_dep| {
step.root_module.addImport( step.root_module.addImport(

View File

@ -26,6 +26,9 @@ pub const Library = struct {
/// Path to a directory with the headers. /// Path to a directory with the headers.
headers: LazyPath, headers: LazyPath,
/// Path to a debug symbols file (.dSYM) if available.
dsym: ?LazyPath,
}; };
step: *Step, step: *Step,
@ -52,6 +55,10 @@ pub fn create(b: *std.Build, opts: Options) *XCFrameworkStep {
run.addFileArg(lib.library); run.addFileArg(lib.library);
run.addArg("-headers"); run.addArg("-headers");
run.addFileArg(lib.headers); run.addFileArg(lib.headers);
if (lib.dsym) |dsym| {
run.addArg("-debug-symbols");
run.addFileArg(dsym);
}
} }
run.addArg("-output"); run.addArg("-output");
run.addArg(opts.out_path); run.addArg(opts.out_path);

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Config = @import("../config/Config.zig"); const Config = @import("../config/Config.zig");
const Action = @import("../cli/action.zig").Action; const Action = @import("../cli.zig").ghostty.Action;
/// A bash completions configuration that contains all the available commands /// A bash completions configuration that contains all the available commands
/// and options. /// and options.

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Config = @import("../config/Config.zig"); const Config = @import("../config/Config.zig");
const Action = @import("../cli/action.zig").Action; const Action = @import("../cli.zig").ghostty.Action;
/// A fish completions configuration that contains all the available commands /// A fish completions configuration that contains all the available commands
/// and options. /// and options.

View File

@ -2,7 +2,7 @@ const std = @import("std");
const help_strings = @import("help_strings"); const help_strings = @import("help_strings");
const build_config = @import("../../build_config.zig"); const build_config = @import("../../build_config.zig");
const Config = @import("../../config/Config.zig"); const Config = @import("../../config/Config.zig");
const Action = @import("../../cli/action.zig").Action; const Action = @import("../../cli/ghostty.zig").Action;
const KeybindAction = @import("../../input/Binding.zig").Action; const KeybindAction = @import("../../input/Binding.zig").Action;
pub fn substitute(alloc: std.mem.Allocator, input: []const u8, writer: anytype) !void { pub fn substitute(alloc: std.mem.Allocator, input: []const u8, writer: anytype) !void {

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Config = @import("../config/Config.zig"); const Config = @import("../config/Config.zig");
const Action = @import("../cli/action.zig").Action; const Action = @import("../cli.zig").ghostty.Action;
/// A zsh completions configuration that contains all the available commands /// A zsh completions configuration that contains all the available commands
/// and options. /// and options.

View File

@ -1,7 +1,8 @@
const diags = @import("cli/diagnostics.zig"); const diags = @import("cli/diagnostics.zig");
pub const args = @import("cli/args.zig"); pub const args = @import("cli/args.zig");
pub const Action = @import("cli/action.zig").Action; pub const action = @import("cli/action.zig");
pub const ghostty = @import("cli/ghostty.zig");
pub const CompatibilityHandler = args.CompatibilityHandler; pub const CompatibilityHandler = args.CompatibilityHandler;
pub const compatibilityRenamed = args.compatibilityRenamed; pub const compatibilityRenamed = args.compatibilityRenamed;
pub const DiagnosticList = diags.DiagnosticList; pub const DiagnosticList = diags.DiagnosticList;

View File

@ -1,320 +1,277 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const help_strings = @import("help_strings");
const list_fonts = @import("list_fonts.zig"); pub const DetectError = error{
const help = @import("help.zig"); /// Multiple actions were detected. You can specify at most one
const version = @import("version.zig"); /// action on the CLI otherwise the behavior desired is ambiguous.
const list_keybinds = @import("list_keybinds.zig"); MultipleActions,
const list_themes = @import("list_themes.zig");
const list_colors = @import("list_colors.zig");
const list_actions = @import("list_actions.zig");
const ssh_cache = @import("ssh_cache.zig");
const edit_config = @import("edit_config.zig");
const show_config = @import("show_config.zig");
const validate_config = @import("validate_config.zig");
const crash_report = @import("crash_report.zig");
const show_face = @import("show_face.zig");
const boo = @import("boo.zig");
/// Special commands that can be invoked via CLI flags. These are all /// An unknown action was specified.
/// invoked by using `+<action>` as a CLI flag. The only exception is InvalidAction,
/// "version" which can be invoked additionally with `--version`.
pub const Action = enum {
/// Output the version and exit
version,
/// Output help information for the CLI or configuration
help,
/// List available fonts
@"list-fonts",
/// List available keybinds
@"list-keybinds",
/// List available themes
@"list-themes",
/// List named RGB colors
@"list-colors",
/// List keybind actions
@"list-actions",
/// Manage SSH terminfo cache for automatic remote host setup
@"ssh-cache",
/// Edit the config file in the configured terminal editor.
@"edit-config",
/// Dump the config to stdout
@"show-config",
// Validate passed config file
@"validate-config",
// Show which font face Ghostty loads a codepoint from.
@"show-face",
// List, (eventually) view, and (eventually) send crash reports.
@"crash-report",
// Boo!
boo,
pub const Error = error{
/// Multiple actions were detected. You can specify at most one
/// action on the CLI otherwise the behavior desired is ambiguous.
MultipleActions,
/// An unknown action was specified.
InvalidAction,
};
/// This should be returned by actions that want to print the help text.
pub const help_error = error.ActionHelpRequested;
/// Detect the action from CLI args.
pub fn detectCLI(alloc: Allocator) !?Action {
var iter = try std.process.argsWithAllocator(alloc);
defer iter.deinit();
return try detectIter(&iter);
}
/// Detect the action from any iterator, used primarily for tests.
pub fn detectIter(iter: anytype) Error!?Action {
var pending_help: bool = false;
var pending: ?Action = null;
while (iter.next()) |arg| {
// If we see a "-e" and we haven't seen a command yet, then
// we are done looking for commands. This special case enables
// `ghostty -e ghostty +command`. If we've seen a command we
// still want to keep looking because
// `ghostty +command -e +command` is invalid.
if (std.mem.eql(u8, arg, "-e") and pending == null) return null;
// Special case, --version always outputs the version no
// matter what, no matter what other args exist.
if (std.mem.eql(u8, arg, "--version")) return .version;
// --help matches "help" but if a subcommand is specified
// then we match the subcommand.
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
pending_help = true;
continue;
}
// Commands must start with "+"
if (arg.len == 0 or arg[0] != '+') continue;
if (pending != null) return Error.MultipleActions;
pending = std.meta.stringToEnum(Action, arg[1..]) orelse return Error.InvalidAction;
}
// If we have an action, we always return that action, even if we've
// seen "--help" or "-h" because the action may have its own help text.
if (pending != null) return pending;
// If we've seen "--help" or "-h" then we return the help action.
if (pending_help) return .help;
return pending;
}
/// Run the action. This returns the exit code to exit with.
pub fn run(self: Action, alloc: Allocator) !u8 {
return self.runMain(alloc) catch |err| switch (err) {
// If help is requested, then we use some comptime trickery
// to find this action in the help strings and output that.
help_error => err: {
inline for (@typeInfo(Action).@"enum".fields) |field| {
// Future note: for now we just output the help text directly
// to stdout. In the future we can style this much prettier
// for all commands by just changing this one place.
if (std.mem.eql(u8, field.name, @tagName(self))) {
const stdout = std.io.getStdOut().writer();
const text = @field(help_strings.Action, field.name) ++ "\n";
stdout.writeAll(text) catch |write_err| {
std.log.warn("failed to write help text: {}\n", .{write_err});
break :err 1;
};
break :err 0;
}
}
break :err err;
},
else => err,
};
}
fn runMain(self: Action, alloc: Allocator) !u8 {
return switch (self) {
.version => try version.run(alloc),
.help => try help.run(alloc),
.@"list-fonts" => try list_fonts.run(alloc),
.@"list-keybinds" => try list_keybinds.run(alloc),
.@"list-themes" => try list_themes.run(alloc),
.@"list-colors" => try list_colors.run(alloc),
.@"list-actions" => try list_actions.run(alloc),
.@"ssh-cache" => try ssh_cache.run(alloc),
.@"edit-config" => try edit_config.run(alloc),
.@"show-config" => try show_config.run(alloc),
.@"validate-config" => try validate_config.run(alloc),
.@"crash-report" => try crash_report.run(alloc),
.@"show-face" => try show_face.run(alloc),
.boo => try boo.run(alloc),
};
}
/// Returns the filename associated with an action. This is a relative
/// path from the root src/ directory.
pub fn file(comptime self: Action) []const u8 {
comptime {
const filename = filename: {
const tag = @tagName(self);
var filename: [tag.len]u8 = undefined;
_ = std.mem.replace(u8, tag, "-", "_", &filename);
break :filename &filename;
};
return "cli/" ++ filename ++ ".zig";
}
}
/// Returns the options of action. Supports generating shell completions
/// without duplicating the mapping from Action to relevant Option
/// @import(..) declaration.
pub fn options(comptime self: Action) type {
comptime {
return switch (self) {
.version => version.Options,
.help => help.Options,
.@"list-fonts" => list_fonts.Options,
.@"list-keybinds" => list_keybinds.Options,
.@"list-themes" => list_themes.Options,
.@"list-colors" => list_colors.Options,
.@"list-actions" => list_actions.Options,
.@"ssh-cache" => ssh_cache.Options,
.@"edit-config" => edit_config.Options,
.@"show-config" => show_config.Options,
.@"validate-config" => validate_config.Options,
.@"crash-report" => crash_report.Options,
.@"show-face" => show_face.Options,
.boo => boo.Options,
};
}
}
}; };
test "parse action none" { /// Detect the action from CLI args.
pub fn detectArgs(comptime E: type, alloc: Allocator) !?E {
var iter = try std.process.argsWithAllocator(alloc);
defer iter.deinit();
return try detectIter(E, &iter);
}
/// Detect the action from any iterator. Each iterator value should yield
/// a CLI argument such as "--foo".
///
/// The comptime type E must be an enum with the available actions.
/// If the type E has a decl `detectSpecialCase`, then it will be called
/// for each argument to allow handling of special cases. The function
/// signature for `detectSpecialCase` should be:
///
/// fn detectSpecialCase(arg: []const u8) ?SpecialCase(E)
///
pub fn detectIter(
comptime E: type,
iter: anytype,
) DetectError!?E {
var fallback: ?E = null;
var pending: ?E = null;
while (iter.next()) |arg| {
// Allow handling of special cases.
if (@hasDecl(E, "detectSpecialCase")) special: {
const special = E.detectSpecialCase(arg) orelse break :special;
switch (special) {
.action => |a| return a,
.fallback => |a| fallback = a,
.abort_if_no_action => if (pending == null) return null,
}
}
// Commands must start with "+"
if (arg.len == 0 or arg[0] != '+') continue;
if (pending != null) return DetectError.MultipleActions;
pending = std.meta.stringToEnum(E, arg[1..]) orelse
return DetectError.InvalidAction;
}
// If we have an action, we always return that action, even if we've
// seen "--help" or "-h" because the action may have its own help text.
if (pending != null) return pending;
// If we have no action but we have a fallback, then we return that.
if (fallback) |a| return a;
return null;
}
/// The action enum E can implement the decl `detectSpecialCase` to
/// return this enum in order to perform various special case actions.
pub fn SpecialCase(comptime E: type) type {
return union(enum) {
/// Immediately return this action.
action: E,
/// Return this action if no other action is found.
fallback: E,
/// If there is no pending action (we haven't seen an action yet)
/// then we should return no action. This is kind of weird but is
/// a special case to allow "-e" in Ghostty.
abort_if_no_action,
};
}
test "detect direct match" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init( var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc, alloc,
"--a=42 --b --b-f=false", "+foo",
); );
defer iter.deinit(); defer iter.deinit();
const action = try Action.detectIter(&iter); const result = try detectIter(Enum, &iter);
try testing.expect(action == null); try testing.expectEqual(Enum.foo, result.?);
} }
test "parse action version" { test "detect invalid match" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
const Enum = enum { foo, bar, baz };
{ var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc,
alloc, "+invalid",
"--a=42 --b --b-f=false --version", );
); defer iter.deinit();
defer iter.deinit(); try testing.expectError(
const action = try Action.detectIter(&iter); DetectError.InvalidAction,
try testing.expect(action.? == .version); detectIter(Enum, &iter),
} );
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--version --a=42 --b --b-f=false",
);
defer iter.deinit();
const action = try Action.detectIter(&iter);
try testing.expect(action.? == .version);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--c=84 --d --version --a=42 --b --b-f=false",
);
defer iter.deinit();
const action = try Action.detectIter(&iter);
try testing.expect(action.? == .version);
}
} }
test "parse action plus" { test "detect multiple actions" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
const Enum = enum { foo, bar, baz };
{ var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc,
alloc, "+foo +bar",
"--a=42 --b --b-f=false +version", );
); defer iter.deinit();
defer iter.deinit(); try testing.expectError(
const action = try Action.detectIter(&iter); DetectError.MultipleActions,
try testing.expect(action.? == .version); detectIter(Enum, &iter),
} );
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"+version --a=42 --b --b-f=false",
);
defer iter.deinit();
const action = try Action.detectIter(&iter);
try testing.expect(action.? == .version);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--c=84 --d +version --a=42 --b --b-f=false",
);
defer iter.deinit();
const action = try Action.detectIter(&iter);
try testing.expect(action.? == .version);
}
} }
test "parse action plus ignores -e" { test "detect no match" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--some-flag",
);
defer iter.deinit();
const result = try detectIter(Enum, &iter);
try testing.expect(result == null);
}
test "detect special case action" {
const testing = std.testing;
const alloc = testing.allocator;
const Enum = enum {
foo,
bar,
fn detectSpecialCase(arg: []const u8) ?SpecialCase(@This()) {
return if (std.mem.eql(u8, arg, "--special"))
.{ .action = .foo }
else
null;
}
};
{ {
var iter = try std.process.ArgIteratorGeneral(.{}).init( var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc, alloc,
"--a=42 -e +version", "--special +bar",
); );
defer iter.deinit(); defer iter.deinit();
const action = try Action.detectIter(&iter); const result = try detectIter(Enum, &iter);
try testing.expect(action == null); try testing.expectEqual(Enum.foo, result.?);
} }
{ {
var iter = try std.process.ArgIteratorGeneral(.{}).init( var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc, alloc,
"+list-fonts --a=42 -e +version", "+bar --special",
); );
defer iter.deinit(); defer iter.deinit();
try testing.expectError( const result = try detectIter(Enum, &iter);
Action.Error.MultipleActions, try testing.expectEqual(Enum.foo, result.?);
Action.detectIter(&iter), }
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"+bar",
); );
defer iter.deinit();
const result = try detectIter(Enum, &iter);
try testing.expectEqual(Enum.bar, result.?);
}
}
test "detect special case fallback" {
const testing = std.testing;
const alloc = testing.allocator;
const Enum = enum {
foo,
bar,
fn detectSpecialCase(arg: []const u8) ?SpecialCase(@This()) {
return if (std.mem.eql(u8, arg, "--special"))
.{ .fallback = .foo }
else
null;
}
};
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--special",
);
defer iter.deinit();
const result = try detectIter(Enum, &iter);
try testing.expectEqual(Enum.foo, result.?);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"+bar --special",
);
defer iter.deinit();
const result = try detectIter(Enum, &iter);
try testing.expectEqual(Enum.bar, result.?);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--special +bar",
);
defer iter.deinit();
const result = try detectIter(Enum, &iter);
try testing.expectEqual(Enum.bar, result.?);
}
}
test "detect special case abort_if_no_action" {
const testing = std.testing;
const alloc = testing.allocator;
const Enum = enum {
foo,
bar,
fn detectSpecialCase(arg: []const u8) ?SpecialCase(@This()) {
return if (std.mem.eql(u8, arg, "-e"))
.abort_if_no_action
else
null;
}
};
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"-e",
);
defer iter.deinit();
const result = try detectIter(Enum, &iter);
try testing.expect(result == null);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"+foo -e",
);
defer iter.deinit();
const result = try detectIter(Enum, &iter);
try testing.expectEqual(Enum.foo, result.?);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"-e +bar",
);
defer iter.deinit();
const result = try detectIter(Enum, &iter);
try testing.expect(result == null);
} }
} }

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const args = @import("args.zig"); const args = @import("args.zig");
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const help_strings = @import("help_strings"); const help_strings = @import("help_strings");
const vaxis = @import("vaxis"); const vaxis = @import("vaxis");

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const args = @import("args.zig"); const args = @import("args.zig");
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const Config = @import("../config.zig").Config; const Config = @import("../config.zig").Config;
const crash = @import("../crash/main.zig"); const crash = @import("../crash/main.zig");

View File

@ -3,7 +3,7 @@ const builtin = @import("builtin");
const assert = std.debug.assert; const assert = std.debug.assert;
const args = @import("args.zig"); const args = @import("args.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const configpkg = @import("../config.zig"); const configpkg = @import("../config.zig");
const internal_os = @import("../os/main.zig"); const internal_os = @import("../os/main.zig");
const Config = configpkg.Config; const Config = configpkg.Config;

290
src/cli/ghostty.zig Normal file
View File

@ -0,0 +1,290 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const help_strings = @import("help_strings");
const actionpkg = @import("action.zig");
const SpecialCase = actionpkg.SpecialCase;
const list_fonts = @import("list_fonts.zig");
const help = @import("help.zig");
const version = @import("version.zig");
const list_keybinds = @import("list_keybinds.zig");
const list_themes = @import("list_themes.zig");
const list_colors = @import("list_colors.zig");
const list_actions = @import("list_actions.zig");
const ssh_cache = @import("ssh_cache.zig");
const edit_config = @import("edit_config.zig");
const show_config = @import("show_config.zig");
const validate_config = @import("validate_config.zig");
const crash_report = @import("crash_report.zig");
const show_face = @import("show_face.zig");
const boo = @import("boo.zig");
/// Special commands that can be invoked via CLI flags. These are all
/// invoked by using `+<action>` as a CLI flag. The only exception is
/// "version" which can be invoked additionally with `--version`.
pub const Action = enum {
/// Output the version and exit
version,
/// Output help information for the CLI or configuration
help,
/// List available fonts
@"list-fonts",
/// List available keybinds
@"list-keybinds",
/// List available themes
@"list-themes",
/// List named RGB colors
@"list-colors",
/// List keybind actions
@"list-actions",
/// Manage SSH terminfo cache for automatic remote host setup
@"ssh-cache",
/// Edit the config file in the configured terminal editor.
@"edit-config",
/// Dump the config to stdout
@"show-config",
// Validate passed config file
@"validate-config",
// Show which font face Ghostty loads a codepoint from.
@"show-face",
// List, (eventually) view, and (eventually) send crash reports.
@"crash-report",
// Boo!
boo,
pub fn detectSpecialCase(arg: []const u8) ?SpecialCase(Action) {
// If we see a "-e" and we haven't seen a command yet, then
// we are done looking for commands. This special case enables
// `ghostty -e ghostty +command`. If we've seen a command we
// still want to keep looking because
// `ghostty +command -e +command` is invalid.
if (std.mem.eql(u8, arg, "-e")) return .abort_if_no_action;
// Special case, --version always outputs the version no
// matter what, no matter what other args exist.
if (std.mem.eql(u8, arg, "--version")) {
return .{ .action = .version };
}
// --help matches "help" but if a subcommand is specified
// then we match the subcommand.
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
return .{ .fallback = .help };
}
return null;
}
/// This should be returned by actions that want to print the help text.
pub const help_error = error.ActionHelpRequested;
/// Run the action. This returns the exit code to exit with.
pub fn run(self: Action, alloc: Allocator) !u8 {
return self.runMain(alloc) catch |err| switch (err) {
// If help is requested, then we use some comptime trickery
// to find this action in the help strings and output that.
help_error => err: {
inline for (@typeInfo(Action).@"enum".fields) |field| {
// Future note: for now we just output the help text directly
// to stdout. In the future we can style this much prettier
// for all commands by just changing this one place.
if (std.mem.eql(u8, field.name, @tagName(self))) {
const stdout = std.io.getStdOut().writer();
const text = @field(help_strings.Action, field.name) ++ "\n";
stdout.writeAll(text) catch |write_err| {
std.log.warn("failed to write help text: {}\n", .{write_err});
break :err 1;
};
break :err 0;
}
}
break :err err;
},
else => err,
};
}
fn runMain(self: Action, alloc: Allocator) !u8 {
return switch (self) {
.version => try version.run(alloc),
.help => try help.run(alloc),
.@"list-fonts" => try list_fonts.run(alloc),
.@"list-keybinds" => try list_keybinds.run(alloc),
.@"list-themes" => try list_themes.run(alloc),
.@"list-colors" => try list_colors.run(alloc),
.@"list-actions" => try list_actions.run(alloc),
.@"ssh-cache" => try ssh_cache.run(alloc),
.@"edit-config" => try edit_config.run(alloc),
.@"show-config" => try show_config.run(alloc),
.@"validate-config" => try validate_config.run(alloc),
.@"crash-report" => try crash_report.run(alloc),
.@"show-face" => try show_face.run(alloc),
.boo => try boo.run(alloc),
};
}
/// Returns the filename associated with an action. This is a relative
/// path from the root src/ directory.
pub fn file(comptime self: Action) []const u8 {
comptime {
const filename = filename: {
const tag = @tagName(self);
var filename: [tag.len]u8 = undefined;
_ = std.mem.replace(u8, tag, "-", "_", &filename);
break :filename &filename;
};
return "cli/" ++ filename ++ ".zig";
}
}
/// Returns the options of action. Supports generating shell completions
/// without duplicating the mapping from Action to relevant Option
/// @import(..) declaration.
pub fn options(comptime self: Action) type {
comptime {
return switch (self) {
.version => version.Options,
.help => help.Options,
.@"list-fonts" => list_fonts.Options,
.@"list-keybinds" => list_keybinds.Options,
.@"list-themes" => list_themes.Options,
.@"list-colors" => list_colors.Options,
.@"list-actions" => list_actions.Options,
.@"ssh-cache" => ssh_cache.Options,
.@"edit-config" => edit_config.Options,
.@"show-config" => show_config.Options,
.@"validate-config" => validate_config.Options,
.@"crash-report" => crash_report.Options,
.@"show-face" => show_face.Options,
.boo => boo.Options,
};
}
}
};
test "parse action none" {
const testing = std.testing;
const alloc = testing.allocator;
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--a=42 --b --b-f=false",
);
defer iter.deinit();
const action = try actionpkg.detectIter(Action, &iter);
try testing.expect(action == null);
}
test "parse action version" {
const testing = std.testing;
const alloc = testing.allocator;
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--a=42 --b --b-f=false --version",
);
defer iter.deinit();
const action = try actionpkg.detectIter(Action, &iter);
try testing.expect(action.? == .version);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--version --a=42 --b --b-f=false",
);
defer iter.deinit();
const action = try actionpkg.detectIter(Action, &iter);
try testing.expect(action.? == .version);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--c=84 --d --version --a=42 --b --b-f=false",
);
defer iter.deinit();
const action = try actionpkg.detectIter(Action, &iter);
try testing.expect(action.? == .version);
}
}
test "parse action plus" {
const testing = std.testing;
const alloc = testing.allocator;
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--a=42 --b --b-f=false +version",
);
defer iter.deinit();
const action = try actionpkg.detectIter(Action, &iter);
try testing.expect(action.? == .version);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"+version --a=42 --b --b-f=false",
);
defer iter.deinit();
const action = try actionpkg.detectIter(Action, &iter);
try testing.expect(action.? == .version);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--c=84 --d +version --a=42 --b --b-f=false",
);
defer iter.deinit();
const action = try actionpkg.detectIter(Action, &iter);
try testing.expect(action.? == .version);
}
}
test "parse action plus ignores -e" {
const testing = std.testing;
const alloc = testing.allocator;
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"--a=42 -e +version",
);
defer iter.deinit();
const action = try actionpkg.detectIter(Action, &iter);
try testing.expect(action == null);
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
alloc,
"+list-fonts --a=42 -e +version",
);
defer iter.deinit();
try testing.expectError(
actionpkg.DetectError.MultipleActions,
actionpkg.detectIter(Action, &iter),
);
}
}

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const args = @import("args.zig"); const args = @import("args.zig");
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
// Note that this options struct doesn't implement the `help` decl like other // Note that this options struct doesn't implement the `help` decl like other
// actions. That is because the help command is special and wants to handle its // actions. That is because the help command is special and wants to handle its

View File

@ -1,6 +1,6 @@
const std = @import("std"); const std = @import("std");
const args = @import("args.zig"); const args = @import("args.zig");
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const helpgen_actions = @import("../input/helpgen_actions.zig"); const helpgen_actions = @import("../input/helpgen_actions.zig");

View File

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const args = @import("args.zig"); const args = @import("args.zig");
const x11_color = @import("../terminal/main.zig").x11_color; const x11_color = @import("../terminal/main.zig").x11_color;

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const args = @import("args.zig"); const args = @import("args.zig");
const font = @import("../font/main.zig"); const font = @import("../font/main.zig");

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const args = @import("args.zig"); const args = @import("args.zig");
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const Arena = std.heap.ArenaAllocator; const Arena = std.heap.ArenaAllocator;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const configpkg = @import("../config.zig"); const configpkg = @import("../config.zig");

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const inputpkg = @import("../input.zig"); const inputpkg = @import("../input.zig");
const args = @import("args.zig"); const args = @import("args.zig");
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const Config = @import("../config/Config.zig"); const Config = @import("../config/Config.zig");
const themepkg = @import("../config/theme.zig"); const themepkg = @import("../config/theme.zig");
const tui = @import("tui.zig"); const tui = @import("tui.zig");

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const args = @import("args.zig"); const args = @import("args.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const configpkg = @import("../config.zig"); const configpkg = @import("../config.zig");
const Config = configpkg.Config; const Config = configpkg.Config;

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const args = @import("args.zig"); const args = @import("args.zig");
const diagnostics = @import("diagnostics.zig"); const diagnostics = @import("diagnostics.zig");
const font = @import("../font/main.zig"); const font = @import("../font/main.zig");

View File

@ -3,7 +3,7 @@ const fs = std.fs;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const xdg = @import("../os/xdg.zig"); const xdg = @import("../os/xdg.zig");
const args = @import("args.zig"); const args = @import("args.zig");
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
pub const Entry = @import("ssh-cache/Entry.zig"); pub const Entry = @import("ssh-cache/Entry.zig");
pub const DiskCache = @import("ssh-cache/DiskCache.zig"); pub const DiskCache = @import("ssh-cache/DiskCache.zig");

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const args = @import("args.zig"); const args = @import("args.zig");
const Action = @import("action.zig").Action; const Action = @import("ghostty.zig").Action;
const Config = @import("../config.zig").Config; const Config = @import("../config.zig").Config;
const cli = @import("../cli.zig"); const cli = @import("../cli.zig");

View File

@ -41,7 +41,7 @@ pub const BackgroundImageFit = Config.BackgroundImageFit;
pub const LinkPreviews = Config.LinkPreviews; pub const LinkPreviews = Config.LinkPreviews;
// Alternate APIs // Alternate APIs
pub const CAPI = @import("config/CAPI.zig"); pub const CApi = @import("config/CApi.zig");
pub const Wasm = if (!builtin.target.cpu.arch.isWasm()) struct {} else @import("config/Wasm.zig"); pub const Wasm = if (!builtin.target.cpu.arch.isWasm()) struct {} else @import("config/Wasm.zig");
test { test {

View File

@ -814,6 +814,22 @@ palette: Palette = .{},
/// On macOS, changing this configuration requires restarting Ghostty completely. /// On macOS, changing this configuration requires restarting Ghostty completely.
@"background-opacity": f64 = 1.0, @"background-opacity": f64 = 1.0,
/// Applies background opacity to cells with an explicit background color
/// set.
///
/// Normally, `background-opacity` is only applied to the window background.
/// If a cell has an explicit background color set, such as red, then that
/// background color will be fully opaque. An effect of this is that some
/// terminal applications that repaint the background color of the terminal
/// such as a Neovim and Tmux may not respect the `background-opacity`
/// (by design).
///
/// Setting this to `true` will apply the `background-opacity` to all cells
/// regardless of whether they have an explicit background color set or not.
///
/// Available since: 1.2.0
@"background-opacity-cells": bool = false,
/// Whether to blur the background when `background-opacity` is less than 1. /// Whether to blur the background when `background-opacity` is less than 1.
/// ///
/// Valid values are: /// Valid values are:

View File

@ -30,7 +30,7 @@ pub const GlobalState = struct {
gpa: ?GPA, gpa: ?GPA,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
action: ?cli.Action, action: ?cli.ghostty.Action,
logging: Logging, logging: Logging,
rlimits: ResourceLimits = .{}, rlimits: ResourceLimits = .{},
@ -92,7 +92,10 @@ pub const GlobalState = struct {
unreachable; unreachable;
// We first try to parse any action that we may be executing. // We first try to parse any action that we may be executing.
self.action = try cli.Action.detectCLI(self.alloc); self.action = try cli.action.detectArgs(
cli.ghostty.Action,
self.alloc,
);
// If we have an action executing, we disable logging by default // If we have an action executing, we disable logging by default
// since we write to stderr we don't want logs messing up our // since we write to stderr we don't want logs messing up our

View File

@ -4,7 +4,7 @@
const std = @import("std"); const std = @import("std");
const Config = @import("config/Config.zig"); const Config = @import("config/Config.zig");
const Action = @import("cli/action.zig").Action; const Action = @import("cli.zig").ghostty.Action;
const KeybindAction = @import("input/Binding.zig").Action; const KeybindAction = @import("input/Binding.zig").Action;
pub fn main() !void { pub fn main() !void {

Some files were not shown because too many files have changed in this diff Show More