mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Merge branch 'main' into ssh-integration
This commit is contained in:
4
.github/pinact.yml
vendored
Normal file
4
.github/pinact.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
version: 3
|
||||
ignore_actions:
|
||||
- name: "DeterminateSystems/nix-installer-action"
|
||||
ref: "main"
|
2
.github/workflows/clean-artifacts.yml
vendored
2
.github/workflows/clean-artifacts.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Remove old artifacts
|
||||
uses: c-hive/gha-remove-artifacts@v1
|
||||
uses: c-hive/gha-remove-artifacts@44fc7acaf1b3d0987da0e8d4707a989d80e9554b # v1.4.0
|
||||
with:
|
||||
age: "1 week"
|
||||
skip-tags: true
|
||||
|
4
.github/workflows/milestone.yml
vendored
4
.github/workflows/milestone.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
name: Milestone Update
|
||||
steps:
|
||||
- name: Set Milestone for PR
|
||||
uses: hustcer/milestone-action@v2
|
||||
uses: hustcer/milestone-action@09bdc6fda0f43a4df28cda5815cc47df74cfdba7 # v2.8
|
||||
if: github.event.pull_request.merged == true
|
||||
with:
|
||||
action: bind-pr # `bind-pr` is the default action
|
||||
@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
# Bind milestone to closed issue that has a merged PR fix
|
||||
- name: Set Milestone for Issue
|
||||
uses: hustcer/milestone-action@v2
|
||||
uses: hustcer/milestone-action@09bdc6fda0f43a4df28cda5815cc47df74cfdba7 # v2.8
|
||||
if: github.event.issue.state == 'closed'
|
||||
with:
|
||||
action: bind-issue
|
||||
|
8
.github/workflows/nix.yml
vendored
8
.github/workflows/nix.yml
vendored
@ -34,18 +34,18 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
2
.github/workflows/publish-tag.yml
vendored
2
.github/workflows/publish-tag.yml
vendored
@ -64,7 +64,7 @@ jobs:
|
||||
mkdir blob
|
||||
mv appcast.xml blob/appcast.xml
|
||||
- name: Upload Appcast to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }}
|
||||
|
20
.github/workflows/release-pr.yml
vendored
20
.github/workflows/release-pr.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [build-macos-debug]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install sentry-cli
|
||||
run: |
|
||||
@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [build-macos]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install sentry-cli
|
||||
run: |
|
||||
@ -51,16 +51,16 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -189,7 +189,7 @@ jobs:
|
||||
cp ghostty-macos-universal.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal.zip
|
||||
cp ghostty-macos-universal-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-dsym.zip
|
||||
- name: Upload to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_PR_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_PR_AWS_KEY }}
|
||||
@ -203,16 +203,16 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -341,7 +341,7 @@ jobs:
|
||||
cp ghostty-macos-universal-debug.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug.zip
|
||||
cp ghostty-macos-universal-debug-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-dsym.zip
|
||||
- name: Upload to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_PR_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_PR_AWS_KEY }}
|
||||
|
36
.github/workflows/release-tag.yml
vendored
36
.github/workflows/release-tag.yml
vendored
@ -56,7 +56,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
@ -80,20 +80,20 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -111,7 +111,7 @@ jobs:
|
||||
nix develop -c minisign -S -m "ghostty-source.tar.gz" -s minisign.key < minisign.password
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: source-tarball
|
||||
path: |-
|
||||
@ -128,12 +128,12 @@ jobs:
|
||||
GHOSTTY_COMMIT: ${{ needs.setup.outputs.commit }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -260,7 +260,7 @@ jobs:
|
||||
zip -9 -r --symlinks ../../../ghostty-macos-universal-dsym.zip Ghostty.app.dSYM/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: macos
|
||||
path: |-
|
||||
@ -277,7 +277,7 @@ jobs:
|
||||
curl -sL https://sentry.io/get-cli/ | bash
|
||||
|
||||
- name: Download macOS Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: macos
|
||||
|
||||
@ -297,10 +297,10 @@ jobs:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Download macOS Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: macos
|
||||
|
||||
@ -331,7 +331,7 @@ jobs:
|
||||
mv appcast_new.xml appcast.xml
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: sparkle
|
||||
path: |-
|
||||
@ -348,17 +348,17 @@ jobs:
|
||||
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
|
||||
steps:
|
||||
- name: Download macOS Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: macos
|
||||
|
||||
- name: Download Sparkle Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: sparkle
|
||||
|
||||
- name: Download Source Tarball Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: source-tarball
|
||||
|
||||
@ -378,7 +378,7 @@ jobs:
|
||||
mv Ghostty.dmg blob/${GHOSTTY_VERSION}/Ghostty.dmg
|
||||
mv appcast.xml blob/${GHOSTTY_VERSION}/appcast-staged.xml
|
||||
- name: Upload to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }}
|
||||
|
50
.github/workflows/release-tip.yml
vendored
50
.github/workflows/release-tip.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [build-macos]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Tip Tag
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [build-macos-debug-slow]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install sentry-cli
|
||||
run: |
|
||||
@ -52,7 +52,7 @@ jobs:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [build-macos-debug-fast]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install sentry-cli
|
||||
run: |
|
||||
@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [build-macos]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install sentry-cli
|
||||
run: |
|
||||
@ -105,17 +105,17 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -132,7 +132,7 @@ jobs:
|
||||
nix develop -c minisign -S -m ghostty-source.tar.gz -s minisign.key < minisign.password
|
||||
|
||||
- name: Update Release
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
with:
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
@ -158,16 +158,16 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -299,7 +299,7 @@ jobs:
|
||||
|
||||
# Update Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
with:
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
@ -331,7 +331,7 @@ jobs:
|
||||
cp Ghostty.dmg blob/${GHOSTTY_COMMIT_LONG}/Ghostty.dmg
|
||||
|
||||
- name: Upload to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }}
|
||||
@ -349,7 +349,7 @@ jobs:
|
||||
cp appcast_new.xml blob/appcast.xml
|
||||
|
||||
- name: Upload Appcast to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }}
|
||||
@ -373,16 +373,16 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -507,7 +507,7 @@ jobs:
|
||||
|
||||
# Update Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
with:
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
@ -524,7 +524,7 @@ jobs:
|
||||
cp ghostty-macos-universal-debug-slow.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow.zip
|
||||
cp ghostty-macos-universal-debug-slow-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow-dsym.zip
|
||||
- name: Upload to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }}
|
||||
@ -548,16 +548,16 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -682,7 +682,7 @@ jobs:
|
||||
|
||||
# Update Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
with:
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
@ -699,7 +699,7 @@ jobs:
|
||||
cp ghostty-macos-universal-debug-fast.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast.zip
|
||||
cp ghostty-macos-universal-debug-fast-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast-dsym.zip
|
||||
- name: Upload to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }}
|
||||
|
209
.github/workflows/test.yml
vendored
209
.github/workflows/test.yml
vendored
@ -27,6 +27,7 @@ jobs:
|
||||
- test-gtk
|
||||
- test-sentry-linux
|
||||
- test-macos
|
||||
- pinact
|
||||
- prettier
|
||||
- alejandra
|
||||
- typos
|
||||
@ -64,20 +65,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -95,20 +96,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -131,20 +132,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -160,20 +161,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -193,20 +194,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -237,20 +238,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -262,7 +263,7 @@ jobs:
|
||||
cp zig-out/dist/*.tar.gz ghostty-source.tar.gz
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: source-tarball
|
||||
path: |-
|
||||
@ -273,13 +274,13 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -313,13 +314,13 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
with:
|
||||
determinate: true
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -353,13 +354,13 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -400,7 +401,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Download Source Tarball Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: source-tarball
|
||||
- name: Extract tarball
|
||||
@ -408,7 +409,7 @@ jobs:
|
||||
mkdir dist
|
||||
tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
@ -420,7 +421,7 @@ jobs:
|
||||
_LXD_SNAP_DEVCGROUP_CONFIG="/var/lib/snapd/cgroup/snap.lxd.device"
|
||||
sudo mkdir -p /var/lib/snapd/cgroup
|
||||
echo 'self-managed=true' | sudo tee "${_LXD_SNAP_DEVCGROUP_CONFIG}"
|
||||
- uses: snapcore/action-build@v1
|
||||
- uses: snapcore/action-build@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1.3.0
|
||||
with:
|
||||
path: dist
|
||||
|
||||
@ -431,7 +432,7 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
# This could be from a script if we wanted to but inlining here for now
|
||||
# in one place.
|
||||
@ -500,20 +501,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -542,20 +543,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -581,20 +582,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -608,13 +609,13 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -637,17 +638,17 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4 # Check out repo so we can lint it
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -656,6 +657,34 @@ jobs:
|
||||
- name: zig fmt
|
||||
run: nix develop -c zig fmt --check .
|
||||
|
||||
pinact:
|
||||
name: "GitHub Actions Pins"
|
||||
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@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
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: pinact check
|
||||
run: nix develop -c pinact run --check
|
||||
|
||||
prettier:
|
||||
if: github.repository == 'ghostty-org/ghostty'
|
||||
runs-on: namespace-profile-ghostty-xsm
|
||||
@ -664,17 +693,17 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4 # Check out repo so we can lint it
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -691,17 +720,17 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4 # Check out repo so we can lint it
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -718,17 +747,17 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4 # Check out repo so we can lint it
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -745,17 +774,17 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4 # Check out repo so we can lint it
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -772,17 +801,17 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4 # Check out repo so we can lint it
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -806,20 +835,20 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v31
|
||||
- uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -834,13 +863,13 @@ jobs:
|
||||
needs: [test, build-dist]
|
||||
steps:
|
||||
- name: Install and configure Namespace CLI
|
||||
uses: namespacelabs/nscloud-setup@v0
|
||||
uses: namespacelabs/nscloud-setup@d1c625762f7c926a54bd39252efff0705fd11c64 # v0.0.10
|
||||
|
||||
- name: Configure Namespace powered Buildx
|
||||
uses: namespacelabs/nscloud-setup-buildx-action@v0
|
||||
uses: namespacelabs/nscloud-setup-buildx-action@01628ae51ea5d6b0c90109c7dccbf511953aff29 # v0.0.18
|
||||
|
||||
- name: Download Source Tarball Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: source-tarball
|
||||
|
||||
@ -850,7 +879,7 @@ jobs:
|
||||
tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: dist
|
||||
file: dist/src/build/docker/debian/Dockerfile
|
||||
@ -865,18 +894,18 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -901,8 +930,8 @@ jobs:
|
||||
runs-on: ${{ matrix.variant.runner }}
|
||||
needs: [flatpak-check-zig-cache, test]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@10a3c29f0162516f0f68006be14c92f34bd4fa6c # v6.5
|
||||
with:
|
||||
bundle: com.mitchellh.ghostty
|
||||
manifest-path: flatpak/com.mitchellh.ghostty.yml
|
||||
|
10
.github/workflows/update-colorschemes.yml
vendored
10
.github/workflows/update-colorschemes.yml
vendored
@ -17,22 +17,22 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.8
|
||||
uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v16
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
@ -60,7 +60,7 @@ jobs:
|
||||
run: nix build .#ghostty
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
title: Update iTerm2 colorschemes
|
||||
base: main
|
||||
|
@ -155,6 +155,7 @@
|
||||
/src/input/KeyEncoder.zig @ghostty-org/terminal
|
||||
/src/terminal/ @ghostty-org/terminal
|
||||
/src/terminfo/ @ghostty-org/terminal
|
||||
/src/termio/ @ghostty-org/terminal
|
||||
/src/unicode/ @ghostty-org/terminal
|
||||
/src/Surface.zig @ghostty-org/terminal
|
||||
/src/surface_mouse.zig @ghostty-org/terminal
|
||||
@ -180,6 +181,7 @@
|
||||
/po/zh_CN.UTF-8.po @ghostty-org/zh_CN
|
||||
/po/ga_IE.UTF-8.po @ghostty-org/ga_IE
|
||||
/po/ko_KR.UTF-8.po @ghostty-org/ko_KR
|
||||
/po/he_IL.UTF-8.po @ghostty-org/he_IL
|
||||
|
||||
# Packaging - Snap
|
||||
/snap/ @ghostty-org/snap
|
||||
|
1
dist/linux/app.desktop.in
vendored
1
dist/linux/app.desktop.in
vendored
@ -19,6 +19,7 @@ X-TerminalArgAppId=--class=
|
||||
X-TerminalArgDir=--working-directory=
|
||||
X-TerminalArgHold=--wait-after-command
|
||||
DBusActivatable=true
|
||||
X-KDE-Shortcuts=Ctrl+Alt+T
|
||||
|
||||
[Desktop Action new-window]
|
||||
Name=New Window
|
||||
|
@ -350,6 +350,11 @@ typedef struct {
|
||||
const char* message;
|
||||
} ghostty_diagnostic_s;
|
||||
|
||||
typedef struct {
|
||||
const char* ptr;
|
||||
uintptr_t len;
|
||||
} ghostty_string_s;
|
||||
|
||||
typedef struct {
|
||||
double tl_px_x;
|
||||
double tl_px_y;
|
||||
@ -662,6 +667,19 @@ typedef struct {
|
||||
bool soft;
|
||||
} ghostty_action_reload_config_s;
|
||||
|
||||
// apprt.action.OpenUrlKind
|
||||
typedef enum {
|
||||
GHOSTTY_ACTION_OPEN_URL_KIND_UNKNOWN,
|
||||
GHOSTTY_ACTION_OPEN_URL_KIND_TEXT,
|
||||
} ghostty_action_open_url_kind_e;
|
||||
|
||||
// apprt.action.OpenUrl.C
|
||||
typedef struct {
|
||||
ghostty_action_open_url_kind_e kind;
|
||||
const char* url;
|
||||
uintptr_t len;
|
||||
} ghostty_action_open_url_s;
|
||||
|
||||
// apprt.Action.Key
|
||||
typedef enum {
|
||||
GHOSTTY_ACTION_QUIT,
|
||||
@ -711,7 +729,8 @@ typedef enum {
|
||||
GHOSTTY_ACTION_RING_BELL,
|
||||
GHOSTTY_ACTION_UNDO,
|
||||
GHOSTTY_ACTION_REDO,
|
||||
GHOSTTY_ACTION_CHECK_FOR_UPDATES
|
||||
GHOSTTY_ACTION_CHECK_FOR_UPDATES,
|
||||
GHOSTTY_ACTION_OPEN_URL,
|
||||
} ghostty_action_tag_e;
|
||||
|
||||
typedef union {
|
||||
@ -739,6 +758,7 @@ typedef union {
|
||||
ghostty_action_color_change_s color_change;
|
||||
ghostty_action_reload_config_s reload_config;
|
||||
ghostty_action_config_change_s config_change;
|
||||
ghostty_action_open_url_s open_url;
|
||||
} ghostty_action_u;
|
||||
|
||||
typedef struct {
|
||||
@ -778,10 +798,11 @@ typedef struct {
|
||||
//-------------------------------------------------------------------
|
||||
// Published API
|
||||
|
||||
int ghostty_init(void);
|
||||
void ghostty_cli_main(uintptr_t, char**);
|
||||
int ghostty_init(uintptr_t, char**);
|
||||
void ghostty_cli_try_action(void);
|
||||
ghostty_info_s ghostty_info(void);
|
||||
const char* ghostty_translate(const char*);
|
||||
void ghostty_string_free(ghostty_string_s);
|
||||
|
||||
ghostty_config_t ghostty_config_new();
|
||||
void ghostty_config_free(ghostty_config_t);
|
||||
@ -796,7 +817,7 @@ ghostty_input_trigger_s ghostty_config_trigger(ghostty_config_t,
|
||||
uintptr_t);
|
||||
uint32_t ghostty_config_diagnostics_count(ghostty_config_t);
|
||||
ghostty_diagnostic_s ghostty_config_get_diagnostic(ghostty_config_t, uint32_t);
|
||||
void ghostty_config_open();
|
||||
ghostty_string_s ghostty_config_open_path(void);
|
||||
|
||||
ghostty_app_t ghostty_app_new(const ghostty_runtime_config_s*,
|
||||
ghostty_config_t);
|
||||
|
@ -48,8 +48,8 @@
|
||||
<string></string>
|
||||
<key>LSEnvironment</key>
|
||||
<dict>
|
||||
<key>GHOSTTY_MAC_APP</key>
|
||||
<string>1</string>
|
||||
<key>GHOSTTY_MAC_LAUNCH_SOURCE</key>
|
||||
<string>app</string>
|
||||
</dict>
|
||||
<key>MDItemKeywords</key>
|
||||
<string>Terminal</string>
|
||||
|
@ -13,6 +13,8 @@
|
||||
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
||||
9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; };
|
||||
A50297352DFA0F3400B4E924 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50297342DFA0F3300B4E924 /* Double+Extension.swift */; };
|
||||
A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */; };
|
||||
A505D21F2E1B6DE00018808F /* NSWorkspace+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A505D21E2E1B6DDC0018808F /* NSWorkspace+Extension.swift */; };
|
||||
A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A511940E2E050590007258CC /* CloseTerminalIntent.swift */; };
|
||||
A51194112E05A483007258CC /* QuickTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194102E05A480007258CC /* QuickTerminalIntent.swift */; };
|
||||
A51194132E05D006007258CC /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194122E05D003007258CC /* Optional+Extension.swift */; };
|
||||
@ -158,6 +160,8 @@
|
||||
857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
|
||||
A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
|
||||
A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileHandle+Extension.swift"; sourceTree = "<group>"; };
|
||||
A505D21E2E1B6DDC0018808F /* NSWorkspace+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSWorkspace+Extension.swift"; sourceTree = "<group>"; };
|
||||
A511940E2E050590007258CC /* CloseTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseTerminalIntent.swift; sourceTree = "<group>"; };
|
||||
A51194102E05A480007258CC /* QuickTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalIntent.swift; sourceTree = "<group>"; };
|
||||
A51194122E05D003007258CC /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = "<group>"; };
|
||||
@ -516,6 +520,7 @@
|
||||
A586366A2DF0A98900E04A10 /* Array+Extension.swift */,
|
||||
A50297342DFA0F3300B4E924 /* Double+Extension.swift */,
|
||||
A586366E2DF25D8300E04A10 /* Duration+Extension.swift */,
|
||||
A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */,
|
||||
A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */,
|
||||
A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */,
|
||||
A51194122E05D003007258CC /* Optional+Extension.swift */,
|
||||
@ -528,6 +533,7 @@
|
||||
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
|
||||
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
|
||||
A5874D9C2DAD785F00E83852 /* NSWindow+Extension.swift */,
|
||||
A505D21E2E1B6DDC0018808F /* NSWorkspace+Extension.swift */,
|
||||
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
||||
A58636722DF4813000E04A10 /* UndoManager+Extension.swift */,
|
||||
A5CC36142C9CDA03004D6760 /* View+Extension.swift */,
|
||||
@ -799,6 +805,7 @@
|
||||
A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */,
|
||||
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
|
||||
A58636732DF4813400E04A10 /* UndoManager+Extension.swift in Sources */,
|
||||
A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */,
|
||||
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
||||
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */,
|
||||
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
|
||||
@ -815,6 +822,7 @@
|
||||
A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */,
|
||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
||||
A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */,
|
||||
A505D21F2E1B6DE00018808F /* NSWorkspace+Extension.swift in Sources */,
|
||||
A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */,
|
||||
A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */,
|
||||
A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */,
|
||||
|
@ -256,9 +256,8 @@ class AppDelegate: NSObject,
|
||||
// Setup signal handlers
|
||||
setupSignals()
|
||||
|
||||
// This is a hack used by our build scripts, specifically `zig build run`,
|
||||
// to force our app to the foreground.
|
||||
if ProcessInfo.processInfo.environment["GHOSTTY_MAC_ACTIVATE"] == "1" {
|
||||
// If we launched via zig run then we need to force foreground.
|
||||
if Ghostty.launchSource == .zig_run {
|
||||
// This never gets called until we click the dock icon. This forces it
|
||||
// activate immediately.
|
||||
applicationDidBecomeActive(.init(name: NSApplication.didBecomeActiveNotification))
|
||||
@ -933,7 +932,7 @@ class AppDelegate: NSObject,
|
||||
//MARK: - IB Actions
|
||||
|
||||
@IBAction func openConfig(_ sender: Any?) {
|
||||
ghostty.openConfig()
|
||||
Ghostty.App.openConfig()
|
||||
}
|
||||
|
||||
@IBAction func reloadConfig(_ sender: Any?) {
|
||||
|
@ -2,13 +2,32 @@ import AppKit
|
||||
import Cocoa
|
||||
import GhosttyKit
|
||||
|
||||
// We put the GHOSTTY_MAC_APP env var into the Info.plist to detect
|
||||
// whether we launch from the app or not. A user can fake this if
|
||||
// they want but they're doing so at their own detriment...
|
||||
let process = ProcessInfo.processInfo
|
||||
if ((process.environment["GHOSTTY_MAC_APP"] ?? "") == "") {
|
||||
ghostty_cli_main(UInt(CommandLine.argc), CommandLine.unsafeArgv)
|
||||
exit(1)
|
||||
// Initialize Ghostty global state. We do this once right away because the
|
||||
// CLI APIs require it and it lets us ensure it is done immediately for the
|
||||
// rest of the app.
|
||||
if ghostty_init(UInt(CommandLine.argc), CommandLine.unsafeArgv) != GHOSTTY_SUCCESS {
|
||||
Ghostty.logger.critical("ghostty_init failed")
|
||||
|
||||
// We also write to stderr if this is executed from the CLI or zig run
|
||||
switch Ghostty.launchSource {
|
||||
case .cli, .zig_run:
|
||||
let stderrHandle = FileHandle.standardError
|
||||
stderrHandle.write(
|
||||
"Ghostty failed to initialize! If you're executing Ghostty from the command line\n" +
|
||||
"then this is usually because an invalid action or multiple actions were specified.\n" +
|
||||
"Actions start with the `+` character.\n\n" +
|
||||
"View all available actions by running `ghostty +help`.\n")
|
||||
exit(1)
|
||||
|
||||
case .app:
|
||||
// For the app we exit immediately. We should handle this case more
|
||||
// gracefully in the future.
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// This will run the CLI action and exit if one was specified. A CLI
|
||||
// action is a command starting with a `+`, such as `ghostty +boo`.
|
||||
ghostty_cli_try_action();
|
||||
|
||||
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
|
||||
|
@ -40,4 +40,34 @@ extension Ghostty.Action {
|
||||
self.amount = c.amount
|
||||
}
|
||||
}
|
||||
|
||||
struct OpenURL {
|
||||
enum Kind {
|
||||
case unknown
|
||||
case text
|
||||
|
||||
init(_ c: ghostty_action_open_url_kind_e) {
|
||||
switch c {
|
||||
case GHOSTTY_ACTION_OPEN_URL_KIND_TEXT:
|
||||
self = .text
|
||||
default:
|
||||
self = .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let kind: Kind
|
||||
let url: String
|
||||
|
||||
init(c: ghostty_action_open_url_s) {
|
||||
self.kind = Kind(c.kind)
|
||||
|
||||
if let urlCString = c.url {
|
||||
let data = Data(bytes: urlCString, count: Int(c.len))
|
||||
self.url = String(data: data, encoding: .utf8) ?? ""
|
||||
} else {
|
||||
self.url = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,12 +45,6 @@ extension Ghostty {
|
||||
}
|
||||
|
||||
init() {
|
||||
// Initialize ghostty global state. This happens once per process.
|
||||
if ghostty_init() != GHOSTTY_SUCCESS {
|
||||
logger.critical("ghostty_init failed, weird things may happen")
|
||||
readiness = .error
|
||||
}
|
||||
|
||||
// Initialize the global configuration.
|
||||
self.config = Config()
|
||||
if self.config.config == nil {
|
||||
@ -120,9 +114,21 @@ extension Ghostty {
|
||||
ghostty_app_tick(app)
|
||||
}
|
||||
|
||||
func openConfig() {
|
||||
guard let app = self.app else { return }
|
||||
ghostty_app_open_config(app)
|
||||
static func openConfig() {
|
||||
let str = Ghostty.AllocatedString(ghostty_config_open_path()).string
|
||||
guard !str.isEmpty else { return }
|
||||
#if os(macOS)
|
||||
let fileURL = URL(fileURLWithPath: str).absoluteString
|
||||
var action = ghostty_action_open_url_s()
|
||||
action.kind = GHOSTTY_ACTION_OPEN_URL_KIND_TEXT
|
||||
fileURL.withCString { cStr in
|
||||
action.url = cStr
|
||||
action.len = UInt(fileURL.count)
|
||||
_ = openURL(action)
|
||||
}
|
||||
#else
|
||||
fatalError("Unsupported platform for opening config file")
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Reload the configuration.
|
||||
@ -494,7 +500,7 @@ extension Ghostty {
|
||||
pwdChanged(app, target: target, v: action.action.pwd)
|
||||
|
||||
case GHOSTTY_ACTION_OPEN_CONFIG:
|
||||
ghostty_config_open()
|
||||
openConfig()
|
||||
|
||||
case GHOSTTY_ACTION_FLOAT_WINDOW:
|
||||
toggleFloatWindow(app, target: target, mode: action.action.float_window)
|
||||
@ -553,6 +559,9 @@ extension Ghostty {
|
||||
case GHOSTTY_ACTION_CHECK_FOR_UPDATES:
|
||||
checkForUpdates(app)
|
||||
|
||||
case GHOSTTY_ACTION_OPEN_URL:
|
||||
return openURL(action.action.open_url)
|
||||
|
||||
case GHOSTTY_ACTION_UNDO:
|
||||
return undo(app, target: target)
|
||||
|
||||
@ -605,6 +614,34 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
private static func openURL(
|
||||
_ v: ghostty_action_open_url_s
|
||||
) -> Bool {
|
||||
let action = Ghostty.Action.OpenURL(c: v)
|
||||
|
||||
// Convert the URL string to a URL object
|
||||
guard let url = URL(string: action.url) else {
|
||||
Ghostty.logger.warning("invalid URL for open URL action: \(action.url)")
|
||||
return false
|
||||
}
|
||||
|
||||
switch action.kind {
|
||||
case .text:
|
||||
// Open with the default text editor
|
||||
if let textEditor = NSWorkspace.shared.defaultTextEditor {
|
||||
NSWorkspace.shared.open([url], withApplicationAt: textEditor, configuration: NSWorkspace.OpenConfiguration())
|
||||
return true
|
||||
}
|
||||
|
||||
case .unknown:
|
||||
break
|
||||
}
|
||||
|
||||
// Open with the default application for the URL
|
||||
NSWorkspace.shared.open(url)
|
||||
return true
|
||||
}
|
||||
|
||||
private static func undo(_ app: ghostty_app_t, target: ghostty_target_s) -> Bool {
|
||||
let undoManager: UndoManager?
|
||||
switch (target.tag) {
|
||||
|
@ -48,8 +48,51 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: General Helpers
|
||||
|
||||
extension Ghostty {
|
||||
enum LaunchSource: String {
|
||||
case cli
|
||||
case app
|
||||
case zig_run
|
||||
}
|
||||
|
||||
/// Returns the mechanism that launched the app. This is based on an env var so
|
||||
/// its up to the env var being set in the correct circumstance.
|
||||
static var launchSource: LaunchSource {
|
||||
guard let envValue = ProcessInfo.processInfo.environment["GHOSTTY_MAC_LAUNCH_SOURCE"] else {
|
||||
// We default to the CLI because the app bundle always sets the
|
||||
// source. If its unset we assume we're in a CLI environment.
|
||||
return .cli
|
||||
}
|
||||
|
||||
// If the env var is set but its unknown then we default back to the app.
|
||||
return LaunchSource(rawValue: envValue) ?? .app
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Swift Types for C Types
|
||||
|
||||
extension Ghostty {
|
||||
class AllocatedString {
|
||||
private let cString: ghostty_string_s
|
||||
|
||||
init(_ c: ghostty_string_s) {
|
||||
self.cString = c
|
||||
}
|
||||
|
||||
var string: String {
|
||||
guard let ptr = cString.ptr else { return "" }
|
||||
let data = Data(bytes: ptr, count: Int(cString.len))
|
||||
return String(data: data, encoding: .utf8) ?? ""
|
||||
}
|
||||
|
||||
deinit {
|
||||
ghostty_string_free(cString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Ghostty {
|
||||
enum SetFloatWIndow {
|
||||
case on
|
||||
|
@ -0,0 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
extension FileHandle: @retroactive TextOutputStream {
|
||||
/// Write a string to a filehandle.
|
||||
public func write(_ string: String) {
|
||||
let data = Data(string.utf8)
|
||||
self.write(data)
|
||||
}
|
||||
}
|
29
macos/Sources/Helpers/Extensions/NSWorkspace+Extension.swift
Normal file
29
macos/Sources/Helpers/Extensions/NSWorkspace+Extension.swift
Normal file
@ -0,0 +1,29 @@
|
||||
import AppKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension NSWorkspace {
|
||||
/// Returns the URL of the default text editor application.
|
||||
/// - Returns: The URL of the default text editor, or nil if no default text editor is found.
|
||||
var defaultTextEditor: URL? {
|
||||
defaultApplicationURL(forContentType: UTType.plainText.identifier)
|
||||
}
|
||||
|
||||
/// Returns the URL of the default application for opening files with the specified content type.
|
||||
/// - Parameter contentType: The content type identifier (UTI) to find the default application for.
|
||||
/// - Returns: The URL of the default application, or nil if no default application is found.
|
||||
func defaultApplicationURL(forContentType contentType: String) -> URL? {
|
||||
return LSCopyDefaultApplicationURLForContentType(
|
||||
contentType as CFString,
|
||||
.all,
|
||||
nil
|
||||
)?.takeRetainedValue() as? URL
|
||||
}
|
||||
|
||||
/// Returns the URL of the default application for opening files with the specified file extension.
|
||||
/// - Parameter ext: The file extension to find the default application for.
|
||||
/// - Returns: The URL of the default application, or nil if no default application is found.
|
||||
func defaultApplicationURL(forExtension ext: String) -> URL? {
|
||||
guard let uti = UTType(filenameExtension: ext) else { return nil}
|
||||
return defaultApplicationURL(forContentType: uti.identifier)
|
||||
}
|
||||
}
|
@ -58,6 +58,7 @@
|
||||
jq,
|
||||
minisign,
|
||||
pandoc,
|
||||
pinact,
|
||||
hyperfine,
|
||||
typos,
|
||||
uv,
|
||||
@ -98,6 +99,7 @@ in
|
||||
# Linting
|
||||
nodePackages.prettier
|
||||
alejandra
|
||||
pinact
|
||||
typos
|
||||
|
||||
# Testing
|
||||
|
@ -92,6 +92,30 @@ pub const Format = enum(c_uint) {
|
||||
_,
|
||||
};
|
||||
|
||||
/// Minification filter for textures.
|
||||
pub const MinFilter = enum(c_int) {
|
||||
nearest = c.GL_NEAREST,
|
||||
linear = c.GL_LINEAR,
|
||||
nearest_mipmap_nearest = c.GL_NEAREST_MIPMAP_NEAREST,
|
||||
linear_mipmap_nearest = c.GL_LINEAR_MIPMAP_NEAREST,
|
||||
nearest_mipmap_linear = c.GL_NEAREST_MIPMAP_LINEAR,
|
||||
linear_mipmap_linear = c.GL_LINEAR_MIPMAP_LINEAR,
|
||||
};
|
||||
|
||||
/// Magnification filter for textures.
|
||||
pub const MagFilter = enum(c_int) {
|
||||
nearest = c.GL_NEAREST,
|
||||
linear = c.GL_LINEAR,
|
||||
};
|
||||
|
||||
/// Texture coordinate wrapping mode.
|
||||
pub const Wrap = enum(c_int) {
|
||||
clamp_to_edge = c.GL_CLAMP_TO_EDGE,
|
||||
clamp_to_border = c.GL_CLAMP_TO_BORDER,
|
||||
mirrored_repeat = c.GL_MIRRORED_REPEAT,
|
||||
repeat = c.GL_REPEAT,
|
||||
};
|
||||
|
||||
/// Data type for texture images.
|
||||
pub const DataType = enum(c_uint) {
|
||||
UnsignedByte = c.GL_UNSIGNED_BYTE,
|
||||
|
298
po/he_IL.UTF-8.po
Normal file
298
po/he_IL.UTF-8.po
Normal file
@ -0,0 +1,298 @@
|
||||
# Hebrew translations for com.mitchellh.ghostty.
|
||||
# Copyright (C) 2025 Mitchell Hashimoto
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Sl (Shahaf Levi), Sl's Repository Ltd <ghostty@slsrepo.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-06-28 17:01+0200\n"
|
||||
"PO-Revision-Date: 2025-03-13 00:00+0000\n"
|
||||
"Last-Translator: Sl (Shahaf Levi), Sl's Repository Ltd <ghostty@slsrepo.com>\n"
|
||||
"Language-Team: Hebrew <he_IL@lists.sourceforge.net>\n"
|
||||
"Language: he\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||
msgid "Change Terminal Title"
|
||||
msgstr "שינוי כותרת המסוף"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
|
||||
msgid "Leave blank to restore the default title."
|
||||
msgstr "השאר/י ריק כדי לשחזר את כותרת ברירת המחדל."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/ui/1.2/ccw-paste.blp:10
|
||||
#: src/apprt/gtk/CloseDialog.zig:44
|
||||
msgid "Cancel"
|
||||
msgstr "ביטול"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
|
||||
msgid "OK"
|
||||
msgstr "אישור"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5
|
||||
msgid "Configuration Errors"
|
||||
msgstr "שגיאות בהגדרות"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
|
||||
msgid ""
|
||||
"One or more configuration errors were found. Please review the errors below, "
|
||||
"and either reload your configuration or ignore these errors."
|
||||
msgstr "נמצאו אחת או יותר שגיאות בהגדרות. אנא בדוק/י את השגיאות המופיעות מטה ולאחר מכן טען/י את ההגדרות מחדש או התעלם/י מהשגיאות."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9
|
||||
msgid "Ignore"
|
||||
msgstr "התעלמות"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10
|
||||
msgid "Reload Configuration"
|
||||
msgstr "טעינה מחדש של ההגדרות"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50
|
||||
msgid "Split Up"
|
||||
msgstr "פיצול למעלה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55
|
||||
msgid "Split Down"
|
||||
msgstr "פיצול למטה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60
|
||||
msgid "Split Left"
|
||||
msgstr "פיצול שמאלה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65
|
||||
msgid "Split Right"
|
||||
msgstr "פיצול ימינה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "הרץ/י פקודה…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
msgid "Copy"
|
||||
msgstr "העתקה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 src/apprt/gtk/ui/1.2/ccw-paste.blp:11
|
||||
msgid "Paste"
|
||||
msgstr "הדבקה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73
|
||||
msgid "Clear"
|
||||
msgstr "ניקוי"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78
|
||||
msgid "Reset"
|
||||
msgstr "איפוס"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42
|
||||
msgid "Split"
|
||||
msgstr "פיצול"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45
|
||||
msgid "Change Title…"
|
||||
msgstr "שינוי כותרת…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59
|
||||
msgid "Tab"
|
||||
msgstr "כרטיסייה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30
|
||||
#: src/apprt/gtk/Window.zig:263
|
||||
msgid "New Tab"
|
||||
msgstr "כרטיסייה חדשה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35
|
||||
msgid "Close Tab"
|
||||
msgstr "סגור/י כרטיסייה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73
|
||||
msgid "Window"
|
||||
msgstr "חלון"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18
|
||||
msgid "New Window"
|
||||
msgstr "חלון חדש"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23
|
||||
msgid "Close Window"
|
||||
msgstr "סגור/י חלון"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
|
||||
msgid "Config"
|
||||
msgstr "הגדרות"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||
msgid "Open Configuration"
|
||||
msgstr "פתיחת ההגדרות"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "לוח פקודות"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
msgstr "בודק המסוף"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||
#: src/apprt/gtk/Window.zig:1036
|
||||
msgid "About Ghostty"
|
||||
msgstr "אודות Ghostty"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112
|
||||
msgid "Quit"
|
||||
msgstr "יציאה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:6
|
||||
msgid "Authorize Clipboard Access"
|
||||
msgstr "אשר/י גישה ללוח ההעתקה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7
|
||||
msgid ""
|
||||
"An application is attempting to read from the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr "יש אפליקציה שמנסה לקרוא מלוח ההעתקה. התוכן הנוכחי של הלוח מופיע למטה."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10
|
||||
msgid "Deny"
|
||||
msgstr "דחייה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:11
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:11
|
||||
msgid "Allow"
|
||||
msgstr "אישור"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "זכור/י את הבחירה עבור פיצול זה"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "טען/י את ההגדרות מחדש כדי להציג את הבקשה הזו שוב"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
msgid ""
|
||||
"An application is attempting to write to the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr "יש אפליקציה שמנסה לכתוב לתוך לוח ההעתקה. התוכן הנוכחי של הלוח מופיע למטה."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
|
||||
msgid "Warning: Potentially Unsafe Paste"
|
||||
msgstr "אזהרה: ההדבקה עלולה להיות מסוכנת"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 src/apprt/gtk/ui/1.2/ccw-paste.blp:7
|
||||
msgid ""
|
||||
"Pasting this text into the terminal may be dangerous as it looks like some "
|
||||
"commands may be executed."
|
||||
msgstr "הדבקת טקסט זה במסוף עלולה להיות מסוכנת, מכיוון שככל הנראה היא תוביל להרצה של פקודות מסוימות."
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
msgstr "תפריט ראשי"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:238
|
||||
msgid "View Open Tabs"
|
||||
msgstr "הצג/י כרטיסיות פתוחות"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:264
|
||||
msgid "New Split"
|
||||
msgstr "פיצול חדש"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:327
|
||||
msgid ""
|
||||
"⚠️ You're running a debug build of Ghostty! Performance will be degraded."
|
||||
msgstr "⚠️ את/ה מריץ/ה גרסת ניפוי שגיאות של Ghostty! הביצועים יהיו ירודים."
|
||||
|
||||
#: src/apprt/gtk/Window.zig:773
|
||||
msgid "Reloaded the configuration"
|
||||
msgstr "ההגדרות הוטענו מחדש"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:1017
|
||||
msgid "Ghostty Developers"
|
||||
msgstr "המפתחים של Ghostty"
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:144
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr "Ghostty: בודק המסוף"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:47
|
||||
msgid "Close"
|
||||
msgstr "סגירה"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:87
|
||||
msgid "Quit Ghostty?"
|
||||
msgstr "לצאת מGhostty?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:88
|
||||
msgid "Close Window?"
|
||||
msgstr "לסגור את החלון?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:89
|
||||
msgid "Close Tab?"
|
||||
msgstr "לסגור את הכרטיסייה?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:90
|
||||
msgid "Close Split?"
|
||||
msgstr "לסגור את הפיצול?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:96
|
||||
msgid "All terminal sessions will be terminated."
|
||||
msgstr "כל הפעלות המסוף יסתיימו."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:97
|
||||
msgid "All terminal sessions in this window will be terminated."
|
||||
msgstr "כל הפעלות המסוף בחלון זה יסתיימו."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:98
|
||||
msgid "All terminal sessions in this tab will be terminated."
|
||||
msgstr "כל הפעלות המסוף בכרטיסייה זו יסתיימו."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:99
|
||||
msgid "The currently running process in this split will be terminated."
|
||||
msgstr "התהליך שרץ כרגע בפיצול זה יסתיים."
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1257
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "הועתק ללוח ההעתקה"
|
@ -270,6 +270,7 @@ const DerivedConfig = struct {
|
||||
title: ?[:0]const u8,
|
||||
title_report: bool,
|
||||
links: []Link,
|
||||
link_previews: configpkg.LinkPreviews,
|
||||
|
||||
const Link = struct {
|
||||
regex: oni.Regex,
|
||||
@ -336,6 +337,7 @@ const DerivedConfig = struct {
|
||||
.title = config.title,
|
||||
.title_report = config.@"title-report",
|
||||
.links = links,
|
||||
.link_previews = config.@"link-previews",
|
||||
|
||||
// Assignments happen sequentially so we have to do this last
|
||||
// so that the memory is captured from allocs above.
|
||||
@ -1242,7 +1244,7 @@ fn mouseRefreshLinks(
|
||||
// Get our link at the current position. This returns null if there
|
||||
// isn't a link OR if we shouldn't be showing links for some reason
|
||||
// (see further comments for cases).
|
||||
const link_: ?apprt.action.MouseOverLink = link: {
|
||||
const link_: ?apprt.action.MouseOverLink, const preview: bool = link: {
|
||||
// If we clicked and our mouse moved cells then we never
|
||||
// highlight links until the mouse is unclicked. This follows
|
||||
// standard macOS and Linux behavior where a click and drag cancels
|
||||
@ -1257,18 +1259,21 @@ fn mouseRefreshLinks(
|
||||
|
||||
if (!click_pt.coord().eql(pos_vp)) {
|
||||
log.debug("mouse moved while left click held, ignoring link hover", .{});
|
||||
break :link null;
|
||||
break :link .{ null, false };
|
||||
}
|
||||
}
|
||||
|
||||
const link = (try self.linkAtPos(pos)) orelse break :link null;
|
||||
const link = (try self.linkAtPos(pos)) orelse break :link .{ null, false };
|
||||
switch (link[0]) {
|
||||
.open => {
|
||||
const str = try self.io.terminal.screen.selectionString(alloc, .{
|
||||
.sel = link[1],
|
||||
.trim = false,
|
||||
});
|
||||
break :link .{ .url = str };
|
||||
break :link .{
|
||||
.{ .url = str },
|
||||
self.config.link_previews == .true,
|
||||
};
|
||||
},
|
||||
|
||||
._open_osc8 => {
|
||||
@ -1276,9 +1281,14 @@ fn mouseRefreshLinks(
|
||||
const pin = link[1].start();
|
||||
const uri = self.osc8URI(pin) orelse {
|
||||
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
||||
break :link null;
|
||||
break :link .{ null, false };
|
||||
};
|
||||
break :link .{
|
||||
.{
|
||||
.url = uri,
|
||||
},
|
||||
self.config.link_previews != .false,
|
||||
};
|
||||
break :link .{ .url = uri };
|
||||
},
|
||||
}
|
||||
};
|
||||
@ -1294,11 +1304,15 @@ fn mouseRefreshLinks(
|
||||
.mouse_shape,
|
||||
.pointer,
|
||||
);
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
link,
|
||||
);
|
||||
|
||||
if (preview) {
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
link,
|
||||
);
|
||||
}
|
||||
|
||||
try self.queueRender();
|
||||
return;
|
||||
}
|
||||
@ -3710,7 +3724,7 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool {
|
||||
.trim = false,
|
||||
});
|
||||
defer self.alloc.free(str);
|
||||
try internal_os.open(self.alloc, .unknown, str);
|
||||
try self.openUrl(.{ .kind = .unknown, .url = str });
|
||||
},
|
||||
|
||||
._open_osc8 => {
|
||||
@ -3718,13 +3732,35 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool {
|
||||
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
||||
return false;
|
||||
};
|
||||
try internal_os.open(self.alloc, .unknown, uri);
|
||||
try self.openUrl(.{ .kind = .unknown, .url = uri });
|
||||
},
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn openUrl(
|
||||
self: *Surface,
|
||||
action: apprt.action.OpenUrl,
|
||||
) !void {
|
||||
// If the apprt handles it then we're done.
|
||||
if (try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.open_url,
|
||||
action,
|
||||
)) return;
|
||||
|
||||
// apprt didn't handle it, fallback to our simple cross-platform
|
||||
// URL opener. We log a warning because we want well-behaved
|
||||
// apprts to handle this themselves.
|
||||
log.warn("apprt did not handle open URL action, falling back to default opener", .{});
|
||||
try internal_os.open(
|
||||
self.alloc,
|
||||
action.kind,
|
||||
action.url,
|
||||
);
|
||||
}
|
||||
|
||||
/// Return the URI for an OSC8 hyperlink at the given position or null
|
||||
/// if there is no hyperlink.
|
||||
fn osc8URI(self: *Surface, pin: terminal.Pin) ?[]const u8 {
|
||||
@ -4443,6 +4479,18 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
return false;
|
||||
},
|
||||
|
||||
.copy_title_to_clipboard => {
|
||||
const title = self.rt_surface.getTitle() orelse return false;
|
||||
if (title.len == 0) return false;
|
||||
|
||||
self.rt_surface.setClipboardString(title, .standard, false) catch |err| {
|
||||
log.err("error copying title to clipboard err={}", .{err});
|
||||
return true;
|
||||
};
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
.paste_from_clipboard => try self.startClipboardRequest(
|
||||
.standard,
|
||||
.{ .paste = {} },
|
||||
@ -4484,6 +4532,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
try self.setFontSize(size);
|
||||
},
|
||||
|
||||
.set_font_size => |points| {
|
||||
log.debug("set font size={d}", .{points});
|
||||
|
||||
var size = self.font_size;
|
||||
size.points = std.math.clamp(points, 1.0, 255.0);
|
||||
try self.setFontSize(size);
|
||||
},
|
||||
|
||||
.prompt_surface_title => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.prompt_title,
|
||||
@ -4923,7 +4979,7 @@ fn writeScreenFile(
|
||||
defer self.alloc.free(pathZ);
|
||||
try self.rt_surface.setClipboardString(pathZ, .standard, false);
|
||||
},
|
||||
.open => try internal_os.open(self.alloc, .text, path),
|
||||
.open => try self.openUrl(.{ .kind = .text, .url = path }),
|
||||
.paste => self.io.queueMessage(try termio.Message.writeReq(
|
||||
self.alloc,
|
||||
path,
|
||||
|
@ -267,6 +267,11 @@ pub const Action = union(Key) {
|
||||
|
||||
check_for_updates,
|
||||
|
||||
/// Open a URL using the native OS mechanisms. On macOS this might be `open`
|
||||
/// or on Linux this might be `xdg-open`. The exact mechanism is up to the
|
||||
/// apprt.
|
||||
open_url: OpenUrl,
|
||||
|
||||
/// Sync with: ghostty_action_tag_e
|
||||
pub const Key = enum(c_int) {
|
||||
quit,
|
||||
@ -317,6 +322,7 @@ pub const Action = union(Key) {
|
||||
undo,
|
||||
redo,
|
||||
check_for_updates,
|
||||
open_url,
|
||||
};
|
||||
|
||||
/// Sync with: ghostty_action_u
|
||||
@ -357,7 +363,11 @@ pub const Action = union(Key) {
|
||||
// For ABI compatibility, we expect that this is our union size.
|
||||
// At the time of writing, we don't promise ABI compatibility
|
||||
// so we can change this but I want to be aware of it.
|
||||
assert(@sizeOf(CValue) == 16);
|
||||
assert(@sizeOf(CValue) == switch (@sizeOf(usize)) {
|
||||
4 => 16,
|
||||
8 => 24,
|
||||
else => unreachable,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the value type for the given key.
|
||||
@ -614,3 +624,44 @@ pub const ConfigChange = struct {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Open a URL
|
||||
pub const OpenUrl = struct {
|
||||
/// The type of data that the URL refers to.
|
||||
kind: Kind,
|
||||
|
||||
/// The URL.
|
||||
url: []const u8,
|
||||
|
||||
/// The type of the data at the URL to open. This is used as a hint to
|
||||
/// potentially open the URL in a different way.
|
||||
///
|
||||
/// Sync with: ghostty_action_open_url_kind_e
|
||||
pub const Kind = enum(c_int) {
|
||||
/// The type is unknown. This is the default and apprts should
|
||||
/// open the URL in the most generic way possible. For example,
|
||||
/// on macOS this would be the equivalent of `open` or on Linux
|
||||
/// this would be `xdg-open`.
|
||||
unknown,
|
||||
|
||||
/// The URL is known to be a text file. In this case, the apprt
|
||||
/// should try to open the URL in a text editor or viewer or
|
||||
/// some equivalent, if possible.
|
||||
text,
|
||||
};
|
||||
|
||||
// Sync with: ghostty_action_open_url_s
|
||||
pub const C = extern struct {
|
||||
kind: Kind,
|
||||
url: [*]const u8,
|
||||
len: usize,
|
||||
};
|
||||
|
||||
pub fn cval(self: OpenUrl) C {
|
||||
return .{
|
||||
.kind = self.kind,
|
||||
.url = self.url.ptr,
|
||||
.len = self.url.len,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -884,7 +884,7 @@ pub const Surface = struct {
|
||||
}
|
||||
|
||||
// Remove this so that running `ghostty` within Ghostty works.
|
||||
env.remove("GHOSTTY_MAC_APP");
|
||||
env.remove("GHOSTTY_MAC_LAUNCH_SOURCE");
|
||||
|
||||
// If we were launched from the desktop then we want to
|
||||
// remove the LANGUAGE env var so that we don't inherit
|
||||
|
@ -496,7 +496,7 @@ pub fn performAction(
|
||||
.resize_split => self.resizeSplit(target, value),
|
||||
.equalize_splits => self.equalizeSplits(target),
|
||||
.goto_split => return self.gotoSplit(target, value),
|
||||
.open_config => try configpkg.edit.open(self.core_app.alloc),
|
||||
.open_config => return self.openConfig(),
|
||||
.config_change => self.configChange(target, value.config),
|
||||
.reload_config => try self.reloadConfig(target, value),
|
||||
.inspector => self.controlInspector(target, value),
|
||||
@ -519,6 +519,7 @@ pub fn performAction(
|
||||
.secure_input => self.setSecureInput(target, value),
|
||||
.ring_bell => try self.ringBell(target),
|
||||
.toggle_command_palette => try self.toggleCommandPalette(target),
|
||||
.open_url => self.openUrl(value),
|
||||
|
||||
// Unimplemented
|
||||
.close_all_windows,
|
||||
@ -1757,3 +1758,34 @@ fn initActions(self: *App) void {
|
||||
action_map.addAction(action.as(gio.Action));
|
||||
}
|
||||
}
|
||||
|
||||
fn openConfig(self: *App) !bool {
|
||||
// Get the config file path
|
||||
const alloc = self.core_app.alloc;
|
||||
const path = configpkg.edit.openPath(alloc) catch |err| {
|
||||
log.warn("error getting config file path: {}", .{err});
|
||||
return false;
|
||||
};
|
||||
defer alloc.free(path);
|
||||
|
||||
// Open it using openURL. "path" isn't actually a URL but
|
||||
// at the time of writing that works just fine for GTK.
|
||||
self.openUrl(.{ .kind = .text, .url = path });
|
||||
return true;
|
||||
}
|
||||
|
||||
fn openUrl(
|
||||
app: *App,
|
||||
value: apprt.action.OpenUrl,
|
||||
) void {
|
||||
// TODO: use https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.OpenURI.html
|
||||
|
||||
// Fallback to the minimal cross-platform way of opening a URL.
|
||||
// This is always a safe fallback and enables for example Windows
|
||||
// to open URLs (GTK on Windows via WSL is a thing).
|
||||
internal_os.open(
|
||||
app.core_app.alloc,
|
||||
value.kind,
|
||||
value.url,
|
||||
) catch |err| log.warn("unable to open url: {}", .{err});
|
||||
}
|
||||
|
@ -214,6 +214,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
{
|
||||
const btn = gtk.MenuButton.new();
|
||||
btn.as(gtk.Widget).setTooltipText(i18n._("Main Menu"));
|
||||
btn.as(gtk.Widget).setCanFocus(0);
|
||||
btn.setIconName("open-menu-symbolic");
|
||||
btn.setPopover(self.titlebar_menu.asWidget());
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
@ -253,6 +254,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
},
|
||||
};
|
||||
|
||||
btn.setCanFocus(0);
|
||||
btn.setFocusOnClick(0);
|
||||
self.headerbar.packEnd(btn);
|
||||
}
|
||||
|
@ -88,6 +88,19 @@ pub fn init(
|
||||
|
||||
// Our step to open the resulting Ghostty app.
|
||||
const open = open: {
|
||||
const disable_save_state = RunStep.create(b, "disable save state");
|
||||
disable_save_state.has_side_effects = true;
|
||||
disable_save_state.addArgs(&.{
|
||||
"/usr/libexec/PlistBuddy",
|
||||
"-c",
|
||||
// We'll have to change this to `Set` if we ever put this
|
||||
// into our Info.plist.
|
||||
"Add :NSQuitAlwaysKeepsWindows bool false",
|
||||
b.fmt("{s}/Contents/Info.plist", .{app_path}),
|
||||
});
|
||||
disable_save_state.expectExitCode(0);
|
||||
disable_save_state.step.dependOn(&build.step);
|
||||
|
||||
const open = RunStep.create(b, "run Ghostty app");
|
||||
open.has_side_effects = true;
|
||||
open.cwd = b.path("");
|
||||
@ -98,22 +111,17 @@ pub fn init(
|
||||
|
||||
// Open depends on the app
|
||||
open.step.dependOn(&build.step);
|
||||
open.step.dependOn(&disable_save_state.step);
|
||||
|
||||
// This overrides our default behavior and forces logs to show
|
||||
// up on stderr (in addition to the centralized macOS log).
|
||||
open.setEnvironmentVariable("GHOSTTY_LOG", "1");
|
||||
|
||||
// This is hack so that we can activate the app and bring it to
|
||||
// the front forcibly even though we're executing directly
|
||||
// via the binary and not launch services.
|
||||
open.setEnvironmentVariable("GHOSTTY_MAC_ACTIVATE", "1");
|
||||
// Configure how we're launching
|
||||
open.setEnvironmentVariable("GHOSTTY_MAC_LAUNCH_SOURCE", "zig_run");
|
||||
|
||||
if (b.args) |args| {
|
||||
open.addArgs(args);
|
||||
} else {
|
||||
// This tricks the app into thinking it's running from the
|
||||
// app bundle so we don't execute our CLI mode.
|
||||
open.setEnvironmentVariable("GHOSTTY_MAC_APP", "1");
|
||||
}
|
||||
|
||||
break :open open;
|
||||
|
@ -760,6 +760,9 @@ pub fn gtkDistResources(
|
||||
});
|
||||
const resources_c = generate_c.addOutputFileArg("ghostty_resources.c");
|
||||
generate_c.addFileArg(gresource_xml);
|
||||
for (gresource.dependencies) |file| {
|
||||
generate_c.addFileInput(b.path(file));
|
||||
}
|
||||
|
||||
const generate_h = b.addSystemCommand(&.{
|
||||
"glib-compile-resources",
|
||||
@ -770,6 +773,9 @@ pub fn gtkDistResources(
|
||||
});
|
||||
const resources_h = generate_h.addOutputFileArg("ghostty_resources.h");
|
||||
generate_h.addFileArg(gresource_xml);
|
||||
for (gresource.dependencies) |file| {
|
||||
generate_h.addFileInput(b.path(file));
|
||||
}
|
||||
|
||||
return .{
|
||||
.resources_c = .{
|
||||
|
@ -15,8 +15,6 @@ pub const Options = struct {};
|
||||
/// The `version` command is used to display information about Ghostty. Recognized as
|
||||
/// either `+version` or `--version`.
|
||||
pub fn run(alloc: Allocator) !u8 {
|
||||
_ = alloc;
|
||||
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
const tty = std.io.getStdOut().isTty();
|
||||
|
||||
@ -34,32 +32,37 @@ pub fn run(alloc: Allocator) !u8 {
|
||||
try stdout.print(" - channel: {s}\n", .{@tagName(build_config.release_channel)});
|
||||
|
||||
try stdout.print("Build Config\n", .{});
|
||||
try stdout.print(" - Zig version: {s}\n", .{builtin.zig_version_string});
|
||||
try stdout.print(" - build mode : {}\n", .{builtin.mode});
|
||||
try stdout.print(" - app runtime: {}\n", .{build_config.app_runtime});
|
||||
try stdout.print(" - font engine: {}\n", .{build_config.font_backend});
|
||||
try stdout.print(" - renderer : {}\n", .{renderer.Renderer});
|
||||
try stdout.print(" - libxev : {s}\n", .{@tagName(xev.backend)});
|
||||
try stdout.print(" - Zig version : {s}\n", .{builtin.zig_version_string});
|
||||
try stdout.print(" - build mode : {}\n", .{builtin.mode});
|
||||
try stdout.print(" - app runtime : {}\n", .{build_config.app_runtime});
|
||||
try stdout.print(" - font engine : {}\n", .{build_config.font_backend});
|
||||
try stdout.print(" - renderer : {}\n", .{renderer.Renderer});
|
||||
try stdout.print(" - libxev : {s}\n", .{@tagName(xev.backend)});
|
||||
if (comptime build_config.app_runtime == .gtk) {
|
||||
try stdout.print(" - desktop env: {s}\n", .{@tagName(internal_os.desktopEnvironment())});
|
||||
try stdout.print(" - GTK version:\n", .{});
|
||||
try stdout.print(" build : {}\n", .{gtk_version.comptime_version});
|
||||
try stdout.print(" runtime : {}\n", .{gtk_version.getRuntimeVersion()});
|
||||
try stdout.print(" - libadwaita : enabled\n", .{});
|
||||
try stdout.print(" build : {}\n", .{adw_version.comptime_version});
|
||||
try stdout.print(" runtime : {}\n", .{adw_version.getRuntimeVersion()});
|
||||
if (comptime builtin.os.tag == .linux) {
|
||||
const kernel_info = internal_os.getKernelInfo(alloc);
|
||||
defer if (kernel_info) |k| alloc.free(k);
|
||||
try stdout.print(" - kernel version: {s}\n", .{kernel_info orelse "Kernel information unavailable"});
|
||||
}
|
||||
try stdout.print(" - desktop env : {s}\n", .{@tagName(internal_os.desktopEnvironment())});
|
||||
try stdout.print(" - GTK version :\n", .{});
|
||||
try stdout.print(" build : {}\n", .{gtk_version.comptime_version});
|
||||
try stdout.print(" runtime : {}\n", .{gtk_version.getRuntimeVersion()});
|
||||
try stdout.print(" - libadwaita : enabled\n", .{});
|
||||
try stdout.print(" build : {}\n", .{adw_version.comptime_version});
|
||||
try stdout.print(" runtime : {}\n", .{adw_version.getRuntimeVersion()});
|
||||
if (comptime build_options.x11) {
|
||||
try stdout.print(" - libX11 : enabled\n", .{});
|
||||
try stdout.print(" - libX11 : enabled\n", .{});
|
||||
} else {
|
||||
try stdout.print(" - libX11 : disabled\n", .{});
|
||||
try stdout.print(" - libX11 : disabled\n", .{});
|
||||
}
|
||||
|
||||
// We say `libwayland` since it is possible to build Ghostty without
|
||||
// Wayland integration but with Wayland-enabled GTK
|
||||
if (comptime build_options.wayland) {
|
||||
try stdout.print(" - libwayland : enabled\n", .{});
|
||||
try stdout.print(" - libwayland : enabled\n", .{});
|
||||
} else {
|
||||
try stdout.print(" - libwayland : disabled\n", .{});
|
||||
try stdout.print(" - libwayland : disabled\n", .{});
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@ -14,6 +14,7 @@ pub const entryFormatter = formatter.entryFormatter;
|
||||
pub const formatEntry = formatter.formatEntry;
|
||||
|
||||
// Field types
|
||||
pub const BoldColor = Config.BoldColor;
|
||||
pub const ClipboardAccess = Config.ClipboardAccess;
|
||||
pub const Command = Config.Command;
|
||||
pub const ConfirmCloseSurface = Config.ConfirmCloseSurface;
|
||||
@ -37,6 +38,7 @@ pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures;
|
||||
pub const WindowPaddingColor = Config.WindowPaddingColor;
|
||||
pub const BackgroundImagePosition = Config.BackgroundImagePosition;
|
||||
pub const BackgroundImageFit = Config.BackgroundImageFit;
|
||||
pub const LinkPreviews = Config.LinkPreviews;
|
||||
|
||||
// Alternate APIs
|
||||
pub const CAPI = @import("config/CAPI.zig");
|
||||
|
@ -1,7 +1,9 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const cli = @import("../cli.zig");
|
||||
const inputpkg = @import("../input.zig");
|
||||
const global = &@import("../global.zig").state;
|
||||
const state = &@import("../global.zig").state;
|
||||
const c = @import("../main_c.zig");
|
||||
|
||||
const Config = @import("Config.zig");
|
||||
const c_get = @import("c_get.zig");
|
||||
@ -12,14 +14,14 @@ const log = std.log.scoped(.config);
|
||||
|
||||
/// Create a new configuration filled with the initial default values.
|
||||
export fn ghostty_config_new() ?*Config {
|
||||
const result = global.alloc.create(Config) catch |err| {
|
||||
const result = state.alloc.create(Config) catch |err| {
|
||||
log.err("error allocating config err={}", .{err});
|
||||
return null;
|
||||
};
|
||||
|
||||
result.* = Config.default(global.alloc) catch |err| {
|
||||
result.* = Config.default(state.alloc) catch |err| {
|
||||
log.err("error creating config err={}", .{err});
|
||||
global.alloc.destroy(result);
|
||||
state.alloc.destroy(result);
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -29,20 +31,20 @@ export fn ghostty_config_new() ?*Config {
|
||||
export fn ghostty_config_free(ptr: ?*Config) void {
|
||||
if (ptr) |v| {
|
||||
v.deinit();
|
||||
global.alloc.destroy(v);
|
||||
state.alloc.destroy(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// Deep clone the configuration.
|
||||
export fn ghostty_config_clone(self: *Config) ?*Config {
|
||||
const result = global.alloc.create(Config) catch |err| {
|
||||
const result = state.alloc.create(Config) catch |err| {
|
||||
log.err("error allocating config err={}", .{err});
|
||||
return null;
|
||||
};
|
||||
|
||||
result.* = self.clone(global.alloc) catch |err| {
|
||||
result.* = self.clone(state.alloc) catch |err| {
|
||||
log.err("error cloning config err={}", .{err});
|
||||
global.alloc.destroy(result);
|
||||
state.alloc.destroy(result);
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -51,7 +53,7 @@ export fn ghostty_config_clone(self: *Config) ?*Config {
|
||||
|
||||
/// Load the configuration from the CLI args.
|
||||
export fn ghostty_config_load_cli_args(self: *Config) void {
|
||||
self.loadCliArgs(global.alloc) catch |err| {
|
||||
self.loadCliArgs(state.alloc) catch |err| {
|
||||
log.err("error loading config err={}", .{err});
|
||||
};
|
||||
}
|
||||
@ -60,7 +62,7 @@ export fn ghostty_config_load_cli_args(self: *Config) void {
|
||||
/// is usually done first. The default file locations are locations
|
||||
/// such as the home directory.
|
||||
export fn ghostty_config_load_default_files(self: *Config) void {
|
||||
self.loadDefaultFiles(global.alloc) catch |err| {
|
||||
self.loadDefaultFiles(state.alloc) catch |err| {
|
||||
log.err("error loading config err={}", .{err});
|
||||
};
|
||||
}
|
||||
@ -69,7 +71,7 @@ export fn ghostty_config_load_default_files(self: *Config) void {
|
||||
/// file locations in the previously loaded configuration. This will
|
||||
/// recursively continue to load up to a built-in limit.
|
||||
export fn ghostty_config_load_recursive_files(self: *Config) void {
|
||||
self.loadRecursiveFiles(global.alloc) catch |err| {
|
||||
self.loadRecursiveFiles(state.alloc) catch |err| {
|
||||
log.err("error loading config err={}", .{err});
|
||||
};
|
||||
}
|
||||
@ -122,10 +124,13 @@ export fn ghostty_config_get_diagnostic(self: *Config, idx: u32) Diagnostic {
|
||||
return .{ .message = message.ptr };
|
||||
}
|
||||
|
||||
export fn ghostty_config_open() void {
|
||||
edit.open(global.alloc) catch |err| {
|
||||
export fn ghostty_config_open_path() c.String {
|
||||
const path = edit.openPath(state.alloc) catch |err| {
|
||||
log.err("error opening config in editor err={}", .{err});
|
||||
return .empty;
|
||||
};
|
||||
|
||||
return .fromSlice(path);
|
||||
}
|
||||
|
||||
/// Sync with ghostty_diagnostic_s
|
||||
|
@ -69,6 +69,10 @@ pub const compatibility = std.StaticStringMap(
|
||||
// this behavior. This applies to selection too.
|
||||
.{ "cursor-invert-fg-bg", compatCursorInvertFgBg },
|
||||
.{ "selection-invert-fg-bg", compatSelectionInvertFgBg },
|
||||
|
||||
// Ghostty 1.2 merged `bold-is-bright` into the new `bold-color`
|
||||
// by setting the value to "bright".
|
||||
.{ "bold-is-bright", compatBoldIsBright },
|
||||
});
|
||||
|
||||
/// The font families to use.
|
||||
@ -435,7 +439,7 @@ pub const compatibility = std.StaticStringMap(
|
||||
/// * `hinting` - Enable or disable hinting. Enabled by default.
|
||||
///
|
||||
/// * `force-autohint` - Always use the freetype auto-hinter instead of
|
||||
/// the font's native hinter. Enabled by default.
|
||||
/// the font's native hinter. Disabled by default.
|
||||
///
|
||||
/// * `monochrome` - Instructs renderer to use 1-bit monochrome rendering.
|
||||
/// This will disable anti-aliasing, and probably not look very good unless
|
||||
@ -1046,6 +1050,14 @@ link: RepeatableLink = .{},
|
||||
/// `link`). If you want to customize URL matching, use `link` and disable this.
|
||||
@"link-url": bool = true,
|
||||
|
||||
/// Show link previews for a matched URL.
|
||||
///
|
||||
/// When true, link previews are shown for all matched URLs. When false, link
|
||||
/// previews are never shown. When set to "osc8", link previews are only shown
|
||||
/// for hyperlinks created with the OSC 8 sequence (in this case, the link text
|
||||
/// can differ from the link destination).
|
||||
@"link-previews": LinkPreviews = .true,
|
||||
|
||||
/// Whether to start the window in a maximized state. This setting applies
|
||||
/// to new windows and does not apply to tabs, splits, etc. However, this setting
|
||||
/// will apply to all new windows, not just the first one.
|
||||
@ -2815,8 +2827,24 @@ else
|
||||
/// notifications using certain escape sequences such as OSC 9 or OSC 777.
|
||||
@"desktop-notifications": bool = true,
|
||||
|
||||
/// If `true`, the bold text will use the bright color palette.
|
||||
@"bold-is-bright": bool = false,
|
||||
/// Modifies the color used for bold text in the terminal.
|
||||
///
|
||||
/// This can be set to a specific color, using the same format as
|
||||
/// `background` or `foreground` (e.g. `#RRGGBB` but other formats
|
||||
/// are also supported; see the aforementioned documentation). If a
|
||||
/// specific color is set, this color will always be used for all
|
||||
/// bold text regardless of the terminal's color scheme.
|
||||
///
|
||||
/// This can also be set to `bright`, which uses the bright color palette
|
||||
/// for bold text. For example, if the text is red, then the bold will
|
||||
/// use the bright red color. The terminal palette is set with `palette`
|
||||
/// but can also be overridden by the terminal application itself using
|
||||
/// escape sequences such as OSC 4. (Since Ghostty 1.2.0, the previous
|
||||
/// configuration `bold-is-bright` is deprecated and replaced by this
|
||||
/// usage).
|
||||
///
|
||||
/// Available since Ghostty 1.2.0.
|
||||
@"bold-color": ?BoldColor = null,
|
||||
|
||||
/// This will be used to set the `TERM` environment variable.
|
||||
/// HACK: We set this with an `xterm` prefix because vim uses that to enable key
|
||||
@ -3921,6 +3949,23 @@ fn compatSelectionInvertFgBg(
|
||||
return true;
|
||||
}
|
||||
|
||||
fn compatBoldIsBright(
|
||||
self: *Config,
|
||||
alloc: Allocator,
|
||||
key: []const u8,
|
||||
value_: ?[]const u8,
|
||||
) bool {
|
||||
_ = alloc;
|
||||
assert(std.mem.eql(u8, key, "bold-is-bright"));
|
||||
|
||||
const set = cli.args.parseBool(value_ orelse "t") catch return false;
|
||||
if (set) {
|
||||
self.@"bold-color" = .bright;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Create a shallow copy of this config. This will share all the memory
|
||||
/// allocated with the previous config but will have a new arena for
|
||||
/// any changes or new allocations. The config should have `deinit`
|
||||
@ -4345,6 +4390,12 @@ pub const WindowSubtitle = enum {
|
||||
@"working-directory",
|
||||
};
|
||||
|
||||
pub const LinkPreviews = enum {
|
||||
false,
|
||||
true,
|
||||
osc8,
|
||||
};
|
||||
|
||||
/// Color represents a color using RGB.
|
||||
///
|
||||
/// This is a packed struct so that the C API to read color values just
|
||||
@ -4542,6 +4593,58 @@ pub const TerminalColor = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents color values that can be used for bold. See `bold-color`.
|
||||
pub const BoldColor = union(enum) {
|
||||
color: Color,
|
||||
bright,
|
||||
|
||||
pub fn parseCLI(input_: ?[]const u8) !BoldColor {
|
||||
const input = input_ orelse return error.ValueRequired;
|
||||
if (std.mem.eql(u8, input, "bright")) return .bright;
|
||||
return .{ .color = try Color.parseCLI(input) };
|
||||
}
|
||||
|
||||
/// Used by Formatter
|
||||
pub fn formatEntry(self: BoldColor, formatter: anytype) !void {
|
||||
switch (self) {
|
||||
.color => try self.color.formatEntry(formatter),
|
||||
.bright => try formatter.formatEntry(
|
||||
[:0]const u8,
|
||||
@tagName(self),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
test "parseCLI" {
|
||||
const testing = std.testing;
|
||||
|
||||
try testing.expectEqual(
|
||||
BoldColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } },
|
||||
try BoldColor.parseCLI("#4e2a84"),
|
||||
);
|
||||
try testing.expectEqual(
|
||||
BoldColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } },
|
||||
try BoldColor.parseCLI("black"),
|
||||
);
|
||||
try testing.expectEqual(
|
||||
BoldColor.bright,
|
||||
try BoldColor.parseCLI("bright"),
|
||||
);
|
||||
|
||||
try testing.expectError(error.InvalidValue, BoldColor.parseCLI("a"));
|
||||
}
|
||||
|
||||
test "formatConfig" {
|
||||
const testing = std.testing;
|
||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
var sc: BoldColor = .bright;
|
||||
try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
|
||||
try testing.expectEqualSlices(u8, "a = bright\n", buf.items);
|
||||
}
|
||||
};
|
||||
|
||||
pub const ColorList = struct {
|
||||
const Self = @This();
|
||||
|
||||
@ -6552,8 +6655,9 @@ pub const RepeatableCommand = struct {
|
||||
try list.parseCLI(alloc, "title:Foo,action:ignore");
|
||||
try list.parseCLI(alloc, "title:Bar,description:bobr,action:text:ale bydle");
|
||||
try list.parseCLI(alloc, "title:Quux,description:boo,action:increase_font_size:2.5");
|
||||
try list.parseCLI(alloc, "title:Baz,description:Raspberry Pie,action:set_font_size:3.14");
|
||||
|
||||
try testing.expectEqual(@as(usize, 3), list.value.items.len);
|
||||
try testing.expectEqual(@as(usize, 4), list.value.items.len);
|
||||
|
||||
try testing.expectEqual(inputpkg.Binding.Action.ignore, list.value.items[0].action);
|
||||
try testing.expectEqualStrings("Foo", list.value.items[0].title);
|
||||
@ -6570,6 +6674,13 @@ pub const RepeatableCommand = struct {
|
||||
try testing.expectEqualStrings("Quux", list.value.items[2].title);
|
||||
try testing.expectEqualStrings("boo", list.value.items[2].description);
|
||||
|
||||
try testing.expectEqual(
|
||||
inputpkg.Binding.Action{ .set_font_size = 3.14 },
|
||||
list.value.items[3].action,
|
||||
);
|
||||
try testing.expectEqualStrings("Baz", list.value.items[3].title);
|
||||
try testing.expectEqualStrings("Raspberry Pie", list.value.items[3].description);
|
||||
|
||||
try list.parseCLI(alloc, "");
|
||||
try testing.expectEqual(@as(usize, 0), list.value.items.len);
|
||||
}
|
||||
@ -7105,7 +7216,7 @@ pub const FreetypeLoadFlags = packed struct {
|
||||
// for Freetype itself. Ghostty hasn't made any opinionated changes
|
||||
// to these defaults.
|
||||
hinting: bool = true,
|
||||
@"force-autohint": bool = true,
|
||||
@"force-autohint": bool = false,
|
||||
monochrome: bool = false,
|
||||
autohint: bool = true,
|
||||
};
|
||||
@ -8235,3 +8346,23 @@ test "compatibility: removed selection-invert-fg-bg" {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test "compatibility: removed bold-is-bright" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
{
|
||||
var cfg = try Config.default(alloc);
|
||||
defer cfg.deinit();
|
||||
var it: TestIterator = .{ .data = &.{
|
||||
"--bold-is-bright",
|
||||
} };
|
||||
try cfg.loadIter(alloc, &it);
|
||||
try cfg.finalize();
|
||||
|
||||
try testing.expectEqual(
|
||||
BoldColor.bright,
|
||||
cfg.@"bold-color",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,18 +5,19 @@ const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const internal_os = @import("../os/main.zig");
|
||||
|
||||
/// Open the configuration in the OS default editor according to the default
|
||||
/// paths the main config file could be in.
|
||||
/// The path to the configuration that should be opened for editing.
|
||||
///
|
||||
/// On Linux, this will open the file at the XDG config path. This is the
|
||||
/// On Linux, this will use the file at the XDG config path. This is the
|
||||
/// only valid path for Linux so we don't need to check for other paths.
|
||||
///
|
||||
/// On macOS, both XDG and AppSupport paths are valid. Because Ghostty
|
||||
/// prioritizes AppSupport over XDG, we will open AppSupport if it exists,
|
||||
/// prioritizes AppSupport over XDG, we will use AppSupport if it exists,
|
||||
/// followed by XDG if it exists, and finally AppSupport if neither exist.
|
||||
/// For the existence check, we also prefer non-empty files over empty
|
||||
/// files.
|
||||
pub fn open(alloc_gpa: Allocator) !void {
|
||||
///
|
||||
/// The returned value is allocated using the provided allocator.
|
||||
pub fn openPath(alloc_gpa: Allocator) ![:0]const u8 {
|
||||
// Use an arena to make memory management easier in here.
|
||||
var arena = ArenaAllocator.init(alloc_gpa);
|
||||
defer arena.deinit();
|
||||
@ -41,7 +42,7 @@ pub fn open(alloc_gpa: Allocator) !void {
|
||||
}
|
||||
};
|
||||
|
||||
try internal_os.open(alloc_gpa, .text, config_path);
|
||||
return try alloc_gpa.dupeZ(u8, config_path);
|
||||
}
|
||||
|
||||
/// Returns the config path to use for open for the current OS.
|
||||
|
@ -69,10 +69,14 @@ pub fn deinit(self: *Collection, alloc: Allocator) void {
|
||||
if (self.load_options) |*v| v.deinit(alloc);
|
||||
}
|
||||
|
||||
pub const AddError = Allocator.Error || error{
|
||||
CollectionFull,
|
||||
DeferredLoadingUnavailable,
|
||||
};
|
||||
pub const AddError =
|
||||
Allocator.Error ||
|
||||
AdjustSizeError ||
|
||||
error{
|
||||
CollectionFull,
|
||||
DeferredLoadingUnavailable,
|
||||
SetSizeFailed,
|
||||
};
|
||||
|
||||
/// Add a face to the collection for the given style. This face will be added
|
||||
/// next in priority if others exist already, i.e. it'll be the _last_ to be
|
||||
@ -81,10 +85,9 @@ pub const AddError = Allocator.Error || error{
|
||||
/// If no error is encountered then the collection takes ownership of the face,
|
||||
/// in which case face will be deallocated when the collection is deallocated.
|
||||
///
|
||||
/// If a loaded face is added to the collection, it should be the same
|
||||
/// size as all the other faces in the collection. This function will not
|
||||
/// verify or modify the size until the size of the entire collection is
|
||||
/// changed.
|
||||
/// If a loaded face is added to the collection, its size will be changed to
|
||||
/// match the size specified in load_options, adjusted for harmonization with
|
||||
/// the primary face.
|
||||
pub fn add(
|
||||
self: *Collection,
|
||||
alloc: Allocator,
|
||||
@ -103,9 +106,107 @@ pub fn add(
|
||||
return error.DeferredLoadingUnavailable;
|
||||
|
||||
try list.append(alloc, face);
|
||||
|
||||
var owned: *Entry = list.at(idx);
|
||||
|
||||
// If the face is already loaded, apply font size adjustment
|
||||
// now, otherwise we'll apply it whenever we do load it.
|
||||
if (owned.getLoaded()) |loaded| {
|
||||
if (try self.adjustedSize(loaded)) |opts| {
|
||||
loaded.setSize(opts.faceOptions()) catch return error.SetSizeFailed;
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .style = style, .idx = @intCast(idx) };
|
||||
}
|
||||
|
||||
pub const AdjustSizeError = font.Face.GetMetricsError;
|
||||
|
||||
// Calculate a size for the provided face that will match it with the primary
|
||||
// font, metrically, to improve consistency with fallback fonts. Right now we
|
||||
// match the font based on the ex height, or the ideograph width if the font
|
||||
// has ideographs in it.
|
||||
//
|
||||
// This returns null if load options is null or if self.load_options is null.
|
||||
//
|
||||
// This is very much like the `font-size-adjust` CSS property in how it works.
|
||||
// ref: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust
|
||||
//
|
||||
// TODO: In the future, provide config options that allow the user to select
|
||||
// which metric should be matched for fallback fonts, instead of hard
|
||||
// coding it as ex height.
|
||||
pub fn adjustedSize(
|
||||
self: *Collection,
|
||||
face: *Face,
|
||||
) AdjustSizeError!?LoadOptions {
|
||||
const load_options = self.load_options orelse return null;
|
||||
|
||||
// We silently do nothing if we can't get the primary
|
||||
// face, because this might be the primary face itself.
|
||||
const primary_face = self.getFace(.{ .idx = 0 }) catch return null;
|
||||
|
||||
// We do nothing if the primary face and this face are the same.
|
||||
if (@intFromPtr(primary_face) == @intFromPtr(face)) return null;
|
||||
|
||||
const primary_metrics = try primary_face.getMetrics();
|
||||
const face_metrics = try face.getMetrics();
|
||||
|
||||
// We use the ex height to match our font sizes, so that the height of
|
||||
// lower-case letters matches between all fonts in the fallback chain.
|
||||
//
|
||||
// We estimate ex height as 0.75 * cap height if it's not specifically
|
||||
// provided, and we estimate cap height as 0.75 * ascent in the same case.
|
||||
//
|
||||
// If the fallback font has an ic_width we prefer that, for normalization
|
||||
// of CJK font sizes when mixed with latin fonts.
|
||||
//
|
||||
// We estimate the ic_width as twice the cell width if it isn't provided.
|
||||
var primary_cap = primary_metrics.cap_height orelse 0.0;
|
||||
if (primary_cap <= 0) primary_cap = primary_metrics.ascent * 0.75;
|
||||
|
||||
var primary_ex = primary_metrics.ex_height orelse 0.0;
|
||||
if (primary_ex <= 0) primary_ex = primary_cap * 0.75;
|
||||
|
||||
var primary_ic = primary_metrics.ic_width orelse 0.0;
|
||||
if (primary_ic <= 0) primary_ic = primary_metrics.cell_width * 2;
|
||||
|
||||
var face_cap = face_metrics.cap_height orelse 0.0;
|
||||
if (face_cap <= 0) face_cap = face_metrics.ascent * 0.75;
|
||||
|
||||
var face_ex = face_metrics.ex_height orelse 0.0;
|
||||
if (face_ex <= 0) face_ex = face_cap * 0.75;
|
||||
|
||||
var face_ic = face_metrics.ic_width orelse 0.0;
|
||||
if (face_ic <= 0) face_ic = face_metrics.cell_width * 2;
|
||||
|
||||
// If the line height of the scaled font would be larger than
|
||||
// the line height of the primary font, we don't want that, so
|
||||
// we take the minimum between matching the ic/ex and the line
|
||||
// height.
|
||||
//
|
||||
// NOTE: We actually allow the line height to be up to 1.2
|
||||
// times the primary line height because empirically
|
||||
// this is usually fine and is better for CJK.
|
||||
//
|
||||
// TODO: We should probably provide a config option that lets
|
||||
// the user pick what metric to use for size adjustment.
|
||||
const scale = @min(
|
||||
1.2 * primary_metrics.lineHeight() / face_metrics.lineHeight(),
|
||||
if (face_metrics.ic_width != null)
|
||||
primary_ic / face_ic
|
||||
else
|
||||
primary_ex / face_ex,
|
||||
);
|
||||
|
||||
// Make a copy of our load options, set the size to the size of
|
||||
// the provided face, and then multiply that by our scaling factor.
|
||||
var opts = load_options;
|
||||
opts.size = face.size;
|
||||
opts.size.points *= @as(f32, @floatCast(scale));
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
/// Return the Face represented by a given Index. The returned pointer
|
||||
/// is only valid as long as this collection is not modified.
|
||||
///
|
||||
@ -129,21 +230,38 @@ pub fn getFace(self: *Collection, index: Index) !*Face {
|
||||
break :item item;
|
||||
};
|
||||
|
||||
return try self.getFaceFromEntry(item);
|
||||
const face = try self.getFaceFromEntry(
|
||||
item,
|
||||
// We only want to adjust the size if this isn't the primary face.
|
||||
index.style != .regular or index.idx > 0,
|
||||
);
|
||||
|
||||
return face;
|
||||
}
|
||||
|
||||
/// Get the face from an entry.
|
||||
///
|
||||
/// This entry must not be an alias.
|
||||
fn getFaceFromEntry(self: *Collection, entry: *Entry) !*Face {
|
||||
fn getFaceFromEntry(
|
||||
self: *Collection,
|
||||
entry: *Entry,
|
||||
/// Whether to adjust the font size to match the primary face after loading.
|
||||
adjust: bool,
|
||||
) !*Face {
|
||||
assert(entry.* != .alias);
|
||||
|
||||
return switch (entry.*) {
|
||||
inline .deferred, .fallback_deferred => |*d, tag| deferred: {
|
||||
const opts = self.load_options orelse
|
||||
return error.DeferredLoadingUnavailable;
|
||||
const face = try d.load(opts.library, opts.faceOptions());
|
||||
var face = try d.load(opts.library, opts.faceOptions());
|
||||
d.deinit();
|
||||
|
||||
// If we need to adjust the size, do so.
|
||||
if (adjust) if (try self.adjustedSize(&face)) |new_opts| {
|
||||
try face.setSize(new_opts.faceOptions());
|
||||
};
|
||||
|
||||
entry.* = switch (tag) {
|
||||
.deferred => .{ .loaded = face },
|
||||
.fallback_deferred => .{ .fallback_loaded = face },
|
||||
@ -247,7 +365,7 @@ pub fn completeStyles(
|
||||
while (it.next()) |entry| {
|
||||
// Load our face. If we fail to load it, we just skip it and
|
||||
// continue on to try the next one.
|
||||
const face = self.getFaceFromEntry(entry) catch |err| {
|
||||
const face = self.getFaceFromEntry(entry, false) catch |err| {
|
||||
log.warn("error loading regular entry={d} err={}", .{
|
||||
it.index - 1,
|
||||
err,
|
||||
@ -371,7 +489,7 @@ fn syntheticBold(self: *Collection, entry: *Entry) !Face {
|
||||
const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
|
||||
|
||||
// Try to bold it.
|
||||
const regular = try self.getFaceFromEntry(entry);
|
||||
const regular = try self.getFaceFromEntry(entry, false);
|
||||
const face = try regular.syntheticBold(opts.faceOptions());
|
||||
|
||||
var buf: [256]u8 = undefined;
|
||||
@ -391,7 +509,7 @@ fn syntheticItalic(self: *Collection, entry: *Entry) !Face {
|
||||
const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
|
||||
|
||||
// Try to italicize it.
|
||||
const regular = try self.getFaceFromEntry(entry);
|
||||
const regular = try self.getFaceFromEntry(entry, false);
|
||||
const face = try regular.syntheticItalic(opts.faceOptions());
|
||||
|
||||
var buf: [256]u8 = undefined;
|
||||
@ -420,9 +538,12 @@ pub fn setSize(self: *Collection, size: DesiredSize) !void {
|
||||
while (it.next()) |array| {
|
||||
var entry_it = array.value.iterator(0);
|
||||
while (entry_it.next()) |entry| switch (entry.*) {
|
||||
.loaded, .fallback_loaded => |*f| try f.setSize(
|
||||
opts.faceOptions(),
|
||||
),
|
||||
.loaded,
|
||||
.fallback_loaded,
|
||||
=> |*f| {
|
||||
const new_opts = try self.adjustedSize(f) orelse opts.*;
|
||||
try f.setSize(new_opts.faceOptions());
|
||||
},
|
||||
|
||||
// Deferred aren't loaded so we don't need to set their size.
|
||||
// The size for when they're loaded is set since `opts` changed.
|
||||
@ -549,6 +670,16 @@ pub const Entry = union(enum) {
|
||||
}
|
||||
}
|
||||
|
||||
/// If this face is loaded, or is an alias to a loaded face,
|
||||
/// then this returns the `Face`, otherwise returns null.
|
||||
pub fn getLoaded(self: *Entry) ?*Face {
|
||||
return switch (self.*) {
|
||||
.deferred, .fallback_deferred => null,
|
||||
.loaded, .fallback_loaded => |*face| face,
|
||||
.alias => |v| v.getLoaded(),
|
||||
};
|
||||
}
|
||||
|
||||
/// True if the entry is deferred.
|
||||
fn isDeferred(self: Entry) bool {
|
||||
return switch (self) {
|
||||
@ -906,12 +1037,13 @@ test "metrics" {
|
||||
|
||||
var c = init();
|
||||
defer c.deinit(alloc);
|
||||
c.load_options = .{ .library = lib };
|
||||
const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 };
|
||||
c.load_options = .{ .library = lib, .size = size };
|
||||
|
||||
_ = try c.add(alloc, .regular, .{ .loaded = try .init(
|
||||
lib,
|
||||
testFont,
|
||||
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
||||
.{ .size = size },
|
||||
) });
|
||||
|
||||
try c.updateMetrics();
|
||||
@ -958,3 +1090,62 @@ test "metrics" {
|
||||
.cursor_height = 34,
|
||||
}, c.metrics);
|
||||
}
|
||||
|
||||
// TODO: Also test CJK fallback sizing, we don't currently have a CJK test font.
|
||||
test "adjusted sizes" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
const testFont = font.embedded.inconsolata;
|
||||
const fallback = font.embedded.monaspace_neon;
|
||||
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var c = init();
|
||||
defer c.deinit(alloc);
|
||||
const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 };
|
||||
c.load_options = .{ .library = lib, .size = size };
|
||||
|
||||
// Add our primary face.
|
||||
_ = try c.add(alloc, .regular, .{ .loaded = try .init(
|
||||
lib,
|
||||
testFont,
|
||||
.{ .size = size },
|
||||
) });
|
||||
|
||||
try c.updateMetrics();
|
||||
|
||||
// Add the fallback face.
|
||||
const fallback_idx = try c.add(alloc, .regular, .{ .loaded = try .init(
|
||||
lib,
|
||||
fallback,
|
||||
.{ .size = size },
|
||||
) });
|
||||
|
||||
// The ex heights should match.
|
||||
{
|
||||
const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics();
|
||||
const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics();
|
||||
|
||||
try std.testing.expectApproxEqAbs(
|
||||
primary_metrics.ex_height.?,
|
||||
fallback_metrics.ex_height.?,
|
||||
// We accept anything within half a pixel.
|
||||
0.5,
|
||||
);
|
||||
}
|
||||
|
||||
// Resize should keep that relationship.
|
||||
try c.setSize(.{ .points = 37, .xdpi = 96, .ydpi = 96 });
|
||||
{
|
||||
const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics();
|
||||
const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics();
|
||||
|
||||
try std.testing.expectApproxEqAbs(
|
||||
primary_metrics.ex_height.?,
|
||||
fallback_metrics.ex_height.?,
|
||||
// We accept anything within half a pixel.
|
||||
0.5,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,3 @@ offset_y: i32,
|
||||
/// be normalized to be between 0 and 1 prior to use in shaders.
|
||||
atlas_x: u32,
|
||||
atlas_y: u32,
|
||||
|
||||
/// horizontal position to increase drawing position for strings
|
||||
advance_x: f32,
|
||||
|
||||
/// Whether we drew this glyph ourselves with the sprite font.
|
||||
sprite: bool = false,
|
||||
|
@ -107,6 +107,19 @@ pub const FaceMetrics = struct {
|
||||
/// a provided ex height metric or measured from the height of the
|
||||
/// lowercase x glyph.
|
||||
ex_height: ?f64 = null,
|
||||
|
||||
/// The width of the character "水" (CJK water ideograph, U+6C34),
|
||||
/// if present. This is used for font size adjustment, to normalize
|
||||
/// the width of CJK fonts mixed with latin fonts.
|
||||
///
|
||||
/// NOTE: IC = Ideograph Character
|
||||
ic_width: ?f64 = null,
|
||||
|
||||
/// Convenience function for getting the line height
|
||||
/// (ascent - descent + line_gap).
|
||||
pub inline fn lineHeight(self: FaceMetrics) f64 {
|
||||
return self.ascent - self.descent + self.line_gap;
|
||||
}
|
||||
};
|
||||
|
||||
/// Calculate our metrics based on values extracted from a font.
|
||||
|
@ -147,6 +147,9 @@ pub const RenderOptions = struct {
|
||||
/// Maximum ratio of width to height when resizing.
|
||||
max_xy_ratio: ?f64 = null,
|
||||
|
||||
/// Maximum number of cells horizontally to use.
|
||||
max_constraint_width: u2 = 2,
|
||||
|
||||
pub const Size = enum {
|
||||
/// Don't change the size of this glyph.
|
||||
none,
|
||||
@ -186,16 +189,26 @@ pub const RenderOptions = struct {
|
||||
pub fn constrain(
|
||||
self: Constraint,
|
||||
glyph: GlyphSize,
|
||||
/// Available width
|
||||
/// Width of one cell.
|
||||
cell_width: f64,
|
||||
/// Available height
|
||||
/// Height of one cell.
|
||||
cell_height: f64,
|
||||
/// Number of cells horizontally available for this glyph.
|
||||
constraint_width: u2,
|
||||
) GlyphSize {
|
||||
var g = glyph;
|
||||
|
||||
const w = cell_width -
|
||||
self.pad_left * cell_width -
|
||||
self.pad_right * cell_width;
|
||||
const available_width =
|
||||
cell_width * @as(f64, @floatFromInt(
|
||||
@min(
|
||||
self.max_constraint_width,
|
||||
constraint_width,
|
||||
),
|
||||
));
|
||||
|
||||
const w = available_width -
|
||||
self.pad_left * available_width -
|
||||
self.pad_right * available_width;
|
||||
const h = cell_height -
|
||||
self.pad_top * cell_height -
|
||||
self.pad_bottom * cell_height;
|
||||
@ -203,7 +216,7 @@ pub const RenderOptions = struct {
|
||||
// Subtract padding from the bearings so that our
|
||||
// alignment and sizing code works correctly. We
|
||||
// re-add before returning.
|
||||
g.x -= self.pad_left * cell_width;
|
||||
g.x -= self.pad_left * available_width;
|
||||
g.y -= self.pad_bottom * cell_height;
|
||||
|
||||
switch (self.size_horizontal) {
|
||||
@ -305,7 +318,7 @@ pub const RenderOptions = struct {
|
||||
}
|
||||
|
||||
// Re-add our padding before returning.
|
||||
g.x += self.pad_left * cell_width;
|
||||
g.x += self.pad_left * available_width;
|
||||
g.y += self.pad_bottom * cell_height;
|
||||
|
||||
return g;
|
||||
|
@ -31,6 +31,9 @@ pub const Face = struct {
|
||||
/// tables).
|
||||
color: ?ColorState = null,
|
||||
|
||||
/// The current size this font is set to.
|
||||
size: font.face.DesiredSize,
|
||||
|
||||
/// True if our build is using Harfbuzz. If we're not, we can avoid
|
||||
/// some Harfbuzz-specific code paths.
|
||||
const harfbuzz_shaper = font.options.backend.hasHarfbuzz();
|
||||
@ -106,6 +109,7 @@ pub const Face = struct {
|
||||
.font = ct_font,
|
||||
.hb_font = hb_font,
|
||||
.color = color,
|
||||
.size = opts.size,
|
||||
};
|
||||
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
||||
|
||||
@ -333,11 +337,10 @@ pub const Face = struct {
|
||||
.offset_y = 0,
|
||||
.atlas_x = 0,
|
||||
.atlas_y = 0,
|
||||
.advance_x = 0,
|
||||
};
|
||||
|
||||
const metrics = opts.grid_metrics;
|
||||
const cell_width: f64 = @floatFromInt(metrics.cell_width * opts.constraint_width);
|
||||
const cell_width: f64 = @floatFromInt(metrics.cell_width);
|
||||
const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
|
||||
const glyph_size = opts.constraint.constrain(
|
||||
@ -349,6 +352,7 @@ pub const Face = struct {
|
||||
},
|
||||
cell_width,
|
||||
cell_height,
|
||||
opts.constraint_width,
|
||||
);
|
||||
|
||||
const width = glyph_size.width;
|
||||
@ -356,8 +360,16 @@ pub const Face = struct {
|
||||
const x = glyph_size.x;
|
||||
const y = glyph_size.y;
|
||||
|
||||
const px_width: u32 = @intFromFloat(@ceil(width));
|
||||
const px_height: u32 = @intFromFloat(@ceil(height));
|
||||
// We have to include the fractional pixels that we won't be offsetting
|
||||
// in our width and height calculations, that is, we offset by the floor
|
||||
// of the bearings when we render the glyph, meaning there's still a bit
|
||||
// of extra width to the area that's drawn in beyond just the width of
|
||||
// the glyph itself, so we include that extra fraction of a pixel when
|
||||
// calculating the width and height here.
|
||||
const frac_x = rect.origin.x - @floor(rect.origin.x);
|
||||
const frac_y = rect.origin.y - @floor(rect.origin.y);
|
||||
const px_width: u32 = @intFromFloat(@ceil(width + frac_x));
|
||||
const px_height: u32 = @intFromFloat(@ceil(height + frac_y));
|
||||
|
||||
// Settings that are specific to if we are rendering text or emoji.
|
||||
const color: struct {
|
||||
@ -475,26 +487,44 @@ pub const Face = struct {
|
||||
// This should be the distance from the left of
|
||||
// the cell to the left of the glyph's bounding box.
|
||||
const offset_x: i32 = offset_x: {
|
||||
var result: i32 = @intFromFloat(@round(x));
|
||||
|
||||
// If our cell was resized then we adjust our glyph's
|
||||
// position relative to the new center. This keeps glyphs
|
||||
// centered in the cell whether it was made wider or narrower.
|
||||
if (metrics.original_cell_width) |original_width| {
|
||||
const before: i32 = @intCast(original_width);
|
||||
const after: i32 = @intCast(metrics.cell_width);
|
||||
// Increase the offset by half of the difference
|
||||
// between the widths to keep things centered.
|
||||
result += @divTrunc(after - before, 2);
|
||||
// If the glyph's advance is narrower than the cell width then we
|
||||
// center the advance of the glyph within the cell width. At first
|
||||
// I implemented this to proportionally scale the center position
|
||||
// of the glyph but that messes up glyphs that are meant to align
|
||||
// vertically with others, so this is a compromise.
|
||||
//
|
||||
// This makes it so that when the `adjust-cell-width` config is
|
||||
// used, or when a fallback font with a different advance width
|
||||
// is used, we don't get weirdly aligned glyphs.
|
||||
//
|
||||
// We don't do this if the constraint has a horizontal alignment,
|
||||
// since in that case the position was already calculated with the
|
||||
// new cell width in mind.
|
||||
if (opts.constraint.align_horizontal == .none) {
|
||||
var advances: [glyphs.len]macos.graphics.Size = undefined;
|
||||
_ = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, &advances);
|
||||
const advance = advances[0].width;
|
||||
const new_advance =
|
||||
cell_width * @as(f64, @floatFromInt(opts.cell_width orelse 1));
|
||||
// If the original advance is greater than the cell width then
|
||||
// it's possible that this is a ligature or other glyph that is
|
||||
// intended to overflow the cell to one side or the other, and
|
||||
// adjusting the bearings could mess that up, so we just leave
|
||||
// it alone if that's the case.
|
||||
//
|
||||
// We also don't want to do anything if the advance is zero or
|
||||
// less, since this is used for stuff like combining characters.
|
||||
if (advance > new_advance or advance <= 0.0) {
|
||||
break :offset_x @intFromFloat(@ceil(x - frac_x));
|
||||
}
|
||||
break :offset_x @intFromFloat(
|
||||
@ceil(x - frac_x + (new_advance - advance) / 2),
|
||||
);
|
||||
} else {
|
||||
break :offset_x @intFromFloat(@ceil(x - frac_x));
|
||||
}
|
||||
|
||||
break :offset_x result;
|
||||
};
|
||||
|
||||
// Get our advance
|
||||
var advances: [glyphs.len]macos.graphics.Size = undefined;
|
||||
_ = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, &advances);
|
||||
|
||||
return .{
|
||||
.width = px_width,
|
||||
.height = px_height,
|
||||
@ -502,7 +532,6 @@ pub const Face = struct {
|
||||
.offset_y = offset_y,
|
||||
.atlas_x = region.x,
|
||||
.atlas_y = region.y,
|
||||
.advance_x = @floatCast(advances[0].width),
|
||||
};
|
||||
}
|
||||
|
||||
@ -734,6 +763,20 @@ pub const Face = struct {
|
||||
break :cell_width max;
|
||||
};
|
||||
|
||||
// Measure "水" (CJK water ideograph, U+6C34) for our ic width.
|
||||
const ic_width: ?f64 = ic_width: {
|
||||
const glyph = self.glyphIndex('水') orelse break :ic_width null;
|
||||
|
||||
var advances: [1]macos.graphics.Size = undefined;
|
||||
_ = ct_font.getAdvancesForGlyphs(
|
||||
.horizontal,
|
||||
&.{@intCast(glyph)},
|
||||
&advances,
|
||||
);
|
||||
|
||||
break :ic_width advances[0].width;
|
||||
};
|
||||
|
||||
return .{
|
||||
.cell_width = cell_width,
|
||||
.ascent = ascent,
|
||||
@ -745,6 +788,7 @@ pub const Face = struct {
|
||||
.strikethrough_thickness = strikethrough_thickness,
|
||||
.cap_height = cap_height,
|
||||
.ex_height = ex_height,
|
||||
.ic_width = ic_width,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,9 @@ pub const Face = struct {
|
||||
bold: bool = false,
|
||||
} = .{},
|
||||
|
||||
/// The current size this font is set to.
|
||||
size: font.face.DesiredSize,
|
||||
|
||||
/// Initialize a new font face with the given source in-memory.
|
||||
pub fn initFile(
|
||||
lib: Library,
|
||||
@ -107,6 +110,7 @@ pub const Face = struct {
|
||||
.hb_font = hb_font,
|
||||
.ft_mutex = ft_mutex,
|
||||
.load_flags = opts.freetype_load_flags,
|
||||
.size = opts.size,
|
||||
};
|
||||
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
||||
|
||||
@ -203,6 +207,7 @@ pub const Face = struct {
|
||||
/// for clearing any glyph caches, font atlas data, etc.
|
||||
pub fn setSize(self: *Face, opts: font.face.Options) !void {
|
||||
try setSize_(self.face, opts.size);
|
||||
self.size = opts.size;
|
||||
}
|
||||
|
||||
fn setSize_(face: freetype.Face, size: font.face.DesiredSize) !void {
|
||||
@ -348,7 +353,7 @@ pub const Face = struct {
|
||||
|
||||
// use options from config
|
||||
.no_hinting = !do_hinting,
|
||||
.force_autohint = !self.load_flags.@"force-autohint",
|
||||
.force_autohint = self.load_flags.@"force-autohint",
|
||||
.no_autohint = !self.load_flags.autohint,
|
||||
|
||||
// NO_SVG set to true because we don't currently support rendering
|
||||
@ -373,7 +378,6 @@ pub const Face = struct {
|
||||
.offset_y = 0,
|
||||
.atlas_x = 0,
|
||||
.atlas_y = 0,
|
||||
.advance_x = 0,
|
||||
};
|
||||
|
||||
// For synthetic bold, we embolden the glyph.
|
||||
@ -390,7 +394,7 @@ pub const Face = struct {
|
||||
// Next we need to apply any constraints.
|
||||
const metrics = opts.grid_metrics;
|
||||
|
||||
const cell_width: f64 = @floatFromInt(metrics.cell_width * opts.constraint_width);
|
||||
const cell_width: f64 = @floatFromInt(metrics.cell_width);
|
||||
const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
|
||||
const glyph_x: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingX);
|
||||
@ -405,6 +409,7 @@ pub const Face = struct {
|
||||
},
|
||||
cell_width,
|
||||
cell_height,
|
||||
opts.constraint_width,
|
||||
);
|
||||
|
||||
const width = glyph_size.width;
|
||||
@ -638,20 +643,40 @@ pub const Face = struct {
|
||||
// This should be the distance from the left of
|
||||
// the cell to the left of the glyph's bounding box.
|
||||
const offset_x: i32 = offset_x: {
|
||||
var result: i32 = @intFromFloat(@floor(x));
|
||||
|
||||
// If our cell was resized then we adjust our glyph's
|
||||
// position relative to the new center. This keeps glyphs
|
||||
// centered in the cell whether it was made wider or narrower.
|
||||
if (metrics.original_cell_width) |original_width| {
|
||||
const before: i32 = @intCast(original_width);
|
||||
const after: i32 = @intCast(metrics.cell_width);
|
||||
// Increase the offset by half of the difference
|
||||
// between the widths to keep things centered.
|
||||
result += @divTrunc(after - before, 2);
|
||||
// If the glyph's advance is narrower than the cell width then we
|
||||
// center the advance of the glyph within the cell width. At first
|
||||
// I implemented this to proportionally scale the center position
|
||||
// of the glyph but that messes up glyphs that are meant to align
|
||||
// vertically with others, so this is a compromise.
|
||||
//
|
||||
// This makes it so that when the `adjust-cell-width` config is
|
||||
// used, or when a fallback font with a different advance width
|
||||
// is used, we don't get weirdly aligned glyphs.
|
||||
//
|
||||
// We don't do this if the constraint has a horizontal alignment,
|
||||
// since in that case the position was already calculated with the
|
||||
// new cell width in mind.
|
||||
if (opts.constraint.align_horizontal == .none) {
|
||||
const advance = f26dot6ToFloat(glyph.*.advance.x);
|
||||
const new_advance =
|
||||
cell_width * @as(f64, @floatFromInt(opts.cell_width orelse 1));
|
||||
// If the original advance is greater than the cell width then
|
||||
// it's possible that this is a ligature or other glyph that is
|
||||
// intended to overflow the cell to one side or the other, and
|
||||
// adjusting the bearings could mess that up, so we just leave
|
||||
// it alone if that's the case.
|
||||
//
|
||||
// We also don't want to do anything if the advance is zero or
|
||||
// less, since this is used for stuff like combining characters.
|
||||
if (advance > new_advance or advance <= 0.0) {
|
||||
break :offset_x @intFromFloat(@floor(x));
|
||||
}
|
||||
break :offset_x @intFromFloat(
|
||||
@floor(x + (new_advance - advance) / 2),
|
||||
);
|
||||
} else {
|
||||
break :offset_x @intFromFloat(@floor(x));
|
||||
}
|
||||
|
||||
break :offset_x result;
|
||||
};
|
||||
|
||||
return Glyph{
|
||||
@ -661,7 +686,6 @@ pub const Face = struct {
|
||||
.offset_y = offset_y,
|
||||
.atlas_x = region.x,
|
||||
.atlas_y = region.y,
|
||||
.advance_x = f26dot6ToFloat(glyph.*.advance.x),
|
||||
};
|
||||
}
|
||||
|
||||
@ -832,7 +856,7 @@ pub const Face = struct {
|
||||
while (c < 127) : (c += 1) {
|
||||
if (face.getCharIndex(c)) |glyph_index| {
|
||||
if (face.loadGlyph(glyph_index, .{
|
||||
.render = true,
|
||||
.render = false,
|
||||
.no_svg = true,
|
||||
})) {
|
||||
max = @max(
|
||||
@ -870,7 +894,7 @@ pub const Face = struct {
|
||||
defer self.ft_mutex.unlock();
|
||||
if (face.getCharIndex('H')) |glyph_index| {
|
||||
if (face.loadGlyph(glyph_index, .{
|
||||
.render = true,
|
||||
.render = false,
|
||||
.no_svg = true,
|
||||
})) {
|
||||
break :cap f26dot6ToF64(face.handle.*.glyph.*.metrics.height);
|
||||
@ -883,7 +907,7 @@ pub const Face = struct {
|
||||
defer self.ft_mutex.unlock();
|
||||
if (face.getCharIndex('x')) |glyph_index| {
|
||||
if (face.loadGlyph(glyph_index, .{
|
||||
.render = true,
|
||||
.render = false,
|
||||
.no_svg = true,
|
||||
})) {
|
||||
break :ex f26dot6ToF64(face.handle.*.glyph.*.metrics.height);
|
||||
@ -894,6 +918,21 @@ pub const Face = struct {
|
||||
};
|
||||
};
|
||||
|
||||
// Measure "水" (CJK water ideograph, U+6C34) for our ic width.
|
||||
const ic_width: ?f64 = ic_width: {
|
||||
self.ft_mutex.lock();
|
||||
defer self.ft_mutex.unlock();
|
||||
|
||||
const glyph = face.getCharIndex('水') orelse break :ic_width null;
|
||||
|
||||
face.loadGlyph(glyph, .{
|
||||
.render = false,
|
||||
.no_svg = true,
|
||||
}) catch break :ic_width null;
|
||||
|
||||
break :ic_width f26dot6ToF64(face.handle.*.glyph.*.advance.x);
|
||||
};
|
||||
|
||||
return .{
|
||||
.cell_width = cell_width,
|
||||
|
||||
@ -909,6 +948,7 @@ pub const Face = struct {
|
||||
|
||||
.cap_height = cap_height,
|
||||
.ex_height = ex_height,
|
||||
.ic_width = ic_width,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -235,7 +235,6 @@ pub const Face = struct {
|
||||
.offset_y = 0,
|
||||
.atlas_x = region.x,
|
||||
.atlas_y = region.y,
|
||||
.advance_x = 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! This is a generate file, produced by nerd_font_codegen.py
|
||||
//! This is a generated file, produced by nerd_font_codegen.py
|
||||
//! DO NOT EDIT BY HAND!
|
||||
//!
|
||||
//! This file provides info extracted from the nerd fonts patcher script,
|
||||
@ -13,6 +13,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .center,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.02,
|
||||
@ -24,24 +25,29 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .cover,
|
||||
.size_vertical = .fit,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .center,
|
||||
.align_vertical = .center,
|
||||
.pad_left = 0.1,
|
||||
.pad_right = 0.1,
|
||||
.pad_top = 0.01,
|
||||
.pad_bottom = 0.01,
|
||||
.pad_top = 0.1,
|
||||
.pad_bottom = 0.1,
|
||||
},
|
||||
0x276c...0x2771,
|
||||
=> .{
|
||||
.size_horizontal = .cover,
|
||||
.size_vertical = .fit,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .center,
|
||||
.align_vertical = .center,
|
||||
.pad_top = 0.15,
|
||||
.pad_bottom = 0.15,
|
||||
},
|
||||
0xe0b0,
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .start,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.06,
|
||||
@ -54,6 +60,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .start,
|
||||
.align_vertical = .center,
|
||||
.max_xy_ratio = 0.7,
|
||||
@ -62,6 +69,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .end,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.06,
|
||||
@ -74,6 +82,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .end,
|
||||
.align_vertical = .center,
|
||||
.max_xy_ratio = 0.7,
|
||||
@ -82,6 +91,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .start,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.06,
|
||||
@ -94,6 +104,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .start,
|
||||
.align_vertical = .center,
|
||||
.max_xy_ratio = 0.5,
|
||||
@ -102,6 +113,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .end,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.06,
|
||||
@ -114,6 +126,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .end,
|
||||
.align_vertical = .center,
|
||||
.max_xy_ratio = 0.5,
|
||||
@ -123,6 +136,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .start,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.05,
|
||||
@ -135,6 +149,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .start,
|
||||
.align_vertical = .center,
|
||||
},
|
||||
@ -143,6 +158,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .end,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.05,
|
||||
@ -155,6 +171,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .end,
|
||||
.align_vertical = .center,
|
||||
},
|
||||
@ -204,8 +221,8 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
.align_vertical = .center,
|
||||
.pad_left = 0.03,
|
||||
.pad_right = 0.03,
|
||||
.pad_top = 0.01,
|
||||
.pad_bottom = 0.01,
|
||||
.pad_top = 0.03,
|
||||
.pad_bottom = 0.03,
|
||||
.max_xy_ratio = 0.86,
|
||||
},
|
||||
0xe0c5,
|
||||
@ -216,8 +233,8 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
.align_vertical = .center,
|
||||
.pad_left = 0.03,
|
||||
.pad_right = 0.03,
|
||||
.pad_top = 0.01,
|
||||
.pad_bottom = 0.01,
|
||||
.pad_top = 0.03,
|
||||
.pad_bottom = 0.03,
|
||||
.max_xy_ratio = 0.86,
|
||||
},
|
||||
0xe0c6,
|
||||
@ -228,8 +245,8 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
.align_vertical = .center,
|
||||
.pad_left = 0.03,
|
||||
.pad_right = 0.03,
|
||||
.pad_top = 0.01,
|
||||
.pad_bottom = 0.01,
|
||||
.pad_top = 0.03,
|
||||
.pad_bottom = 0.03,
|
||||
.max_xy_ratio = 0.78,
|
||||
},
|
||||
0xe0c7,
|
||||
@ -240,8 +257,8 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
.align_vertical = .center,
|
||||
.pad_left = 0.03,
|
||||
.pad_right = 0.03,
|
||||
.pad_top = 0.01,
|
||||
.pad_bottom = 0.01,
|
||||
.pad_top = 0.03,
|
||||
.pad_bottom = 0.03,
|
||||
.max_xy_ratio = 0.78,
|
||||
},
|
||||
0xe0cc,
|
||||
@ -285,6 +302,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .start,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.02,
|
||||
@ -297,6 +315,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .end,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.02,
|
||||
@ -309,6 +328,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .start,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.05,
|
||||
@ -321,6 +341,7 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
=> .{
|
||||
.size_horizontal = .stretch,
|
||||
.size_vertical = .stretch,
|
||||
.max_constraint_width = 1,
|
||||
.align_horizontal = .end,
|
||||
.align_vertical = .center,
|
||||
.pad_left = -0.05,
|
||||
|
@ -1,102 +1,124 @@
|
||||
"""
|
||||
This file is mostly vibe coded because I don't like Python. It extracts the
|
||||
patch sets from the nerd fonts font patcher file in order to extract scaling
|
||||
rules and attributes for different codepoint ranges which it then codegens
|
||||
in to a Zig file with a function that switches over codepoints and returns
|
||||
the attributes and scaling rules.
|
||||
This file extracts the patch sets from the nerd fonts font patcher file in order to
|
||||
extract scaling rules and attributes for different codepoint ranges which it then
|
||||
codegens in to a Zig file with a function that switches over codepoints and returns the
|
||||
attributes and scaling rules.
|
||||
|
||||
This does include an `eval` call! This is spooky, but we trust
|
||||
the nerd fonts code to be safe and not malicious or anything.
|
||||
This does include an `eval` call! This is spooky, but we trust the nerd fonts code to
|
||||
be safe and not malicious or anything.
|
||||
|
||||
This script requires Python 3.12 or greater.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import math
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Literal, TypedDict, cast
|
||||
|
||||
type PatchSetAttributes = dict[Literal["default"] | int, PatchSetAttributeEntry]
|
||||
type AttributeHash = tuple[str | None, str | None, str, float, float, float]
|
||||
type ResolvedSymbol = PatchSetAttributes | PatchSetScaleRules | int | None
|
||||
|
||||
|
||||
class PatchSetScaleRules(TypedDict):
|
||||
ShiftMode: str
|
||||
ScaleGroups: list[list[int] | range]
|
||||
|
||||
|
||||
class PatchSetAttributeEntry(TypedDict):
|
||||
align: str
|
||||
valign: str
|
||||
stretch: str
|
||||
params: dict[str, float | bool]
|
||||
|
||||
|
||||
class PatchSet(TypedDict):
|
||||
SymStart: int
|
||||
SymEnd: int
|
||||
SrcStart: int | None
|
||||
ScaleRules: PatchSetScaleRules | None
|
||||
Attributes: PatchSetAttributes
|
||||
|
||||
|
||||
class PatchSetExtractor(ast.NodeVisitor):
|
||||
def __init__(self):
|
||||
self.symbol_table = {}
|
||||
self.patch_set_values = []
|
||||
def __init__(self) -> None:
|
||||
self.symbol_table: dict[str, ast.expr] = {}
|
||||
self.patch_set_values: list[PatchSet] = []
|
||||
|
||||
def visit_ClassDef(self, node):
|
||||
if node.name == "font_patcher":
|
||||
for item in node.body:
|
||||
if isinstance(item, ast.FunctionDef) and item.name == "setup_patch_set":
|
||||
self.visit_setup_patch_set(item)
|
||||
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
||||
if node.name != "font_patcher":
|
||||
return
|
||||
for item in node.body:
|
||||
if isinstance(item, ast.FunctionDef) and item.name == "setup_patch_set":
|
||||
self.visit_setup_patch_set(item)
|
||||
|
||||
def visit_setup_patch_set(self, node):
|
||||
def visit_setup_patch_set(self, node: ast.FunctionDef) -> None:
|
||||
# First pass: gather variable assignments
|
||||
for stmt in node.body:
|
||||
if isinstance(stmt, ast.Assign):
|
||||
# Store simple variable assignments in the symbol table
|
||||
if len(stmt.targets) == 1 and isinstance(stmt.targets[0], ast.Name):
|
||||
var_name = stmt.targets[0].id
|
||||
self.symbol_table[var_name] = stmt.value
|
||||
match stmt:
|
||||
case ast.Assign(targets=[ast.Name(id=symbol)]):
|
||||
# Store simple variable assignments in the symbol table
|
||||
self.symbol_table[symbol] = stmt.value
|
||||
|
||||
# Second pass: process self.patch_set
|
||||
for stmt in node.body:
|
||||
if isinstance(stmt, ast.Assign):
|
||||
for target in stmt.targets:
|
||||
if isinstance(target, ast.Attribute) and target.attr == "patch_set":
|
||||
if isinstance(stmt.value, ast.List):
|
||||
for elt in stmt.value.elts:
|
||||
if isinstance(elt, ast.Dict):
|
||||
self.process_patch_entry(elt)
|
||||
if not isinstance(stmt, ast.Assign):
|
||||
continue
|
||||
for target in stmt.targets:
|
||||
if (
|
||||
isinstance(target, ast.Attribute)
|
||||
and target.attr == "patch_set"
|
||||
and isinstance(stmt.value, ast.List)
|
||||
):
|
||||
for elt in stmt.value.elts:
|
||||
if isinstance(elt, ast.Dict):
|
||||
self.process_patch_entry(elt)
|
||||
|
||||
def resolve_symbol(self, node):
|
||||
def resolve_symbol(self, node: ast.expr) -> ResolvedSymbol:
|
||||
"""Resolve named variables to their actual values from the symbol table."""
|
||||
if isinstance(node, ast.Name) and node.id in self.symbol_table:
|
||||
return self.safe_literal_eval(self.symbol_table[node.id])
|
||||
return self.safe_literal_eval(node)
|
||||
|
||||
def safe_literal_eval(self, node):
|
||||
def safe_literal_eval(self, node: ast.expr) -> ResolvedSymbol:
|
||||
"""Try to evaluate or stringify an AST node."""
|
||||
try:
|
||||
return ast.literal_eval(node)
|
||||
except Exception:
|
||||
except ValueError:
|
||||
# Spooky eval! But we trust nerd fonts to be safe...
|
||||
if hasattr(ast, "unparse"):
|
||||
return eval(
|
||||
ast.unparse(node), {"box_keep": True}, {"self": SpoofSelf()}
|
||||
ast.unparse(node),
|
||||
{"box_keep": True},
|
||||
{"self": SimpleNamespace(args=SimpleNamespace(careful=True))},
|
||||
)
|
||||
else:
|
||||
return f"<cannot eval: {type(node).__name__}>"
|
||||
msg = f"<cannot eval: {type(node).__name__}>"
|
||||
raise ValueError(msg) from None
|
||||
|
||||
def process_patch_entry(self, dict_node):
|
||||
def process_patch_entry(self, dict_node: ast.Dict) -> None:
|
||||
entry = {}
|
||||
disallowed_key_nodes = frozenset({"Enabled", "Name", "Filename", "Exact"})
|
||||
for key_node, value_node in zip(dict_node.keys, dict_node.values):
|
||||
if isinstance(key_node, ast.Constant) and key_node.value in (
|
||||
"Enabled",
|
||||
"Name",
|
||||
"Filename",
|
||||
"Exact",
|
||||
if (
|
||||
isinstance(key_node, ast.Constant)
|
||||
and key_node.value not in disallowed_key_nodes
|
||||
):
|
||||
continue
|
||||
key = ast.literal_eval(key_node)
|
||||
value = self.resolve_symbol(value_node)
|
||||
entry[key] = value
|
||||
self.patch_set_values.append(entry)
|
||||
key = ast.literal_eval(cast("ast.Constant", key_node))
|
||||
entry[key] = self.resolve_symbol(value_node)
|
||||
self.patch_set_values.append(cast("PatchSet", entry))
|
||||
|
||||
|
||||
def extract_patch_set_values(source_code):
|
||||
def extract_patch_set_values(source_code: str) -> list[PatchSet]:
|
||||
tree = ast.parse(source_code)
|
||||
extractor = PatchSetExtractor()
|
||||
extractor.visit(tree)
|
||||
return extractor.patch_set_values
|
||||
|
||||
|
||||
# We have to spoof `self` and `self.args` for the eval.
|
||||
class SpoofArgs:
|
||||
careful = True
|
||||
|
||||
|
||||
class SpoofSelf:
|
||||
args = SpoofArgs()
|
||||
|
||||
|
||||
def parse_alignment(val):
|
||||
def parse_alignment(val: str) -> str | None:
|
||||
return {
|
||||
"l": ".start",
|
||||
"r": ".end",
|
||||
@ -105,28 +127,24 @@ def parse_alignment(val):
|
||||
}.get(val, ".none")
|
||||
|
||||
|
||||
def get_param(d, key, default):
|
||||
return float(d.get(key, default))
|
||||
|
||||
|
||||
def attr_key(attr):
|
||||
def attr_key(attr: PatchSetAttributeEntry) -> AttributeHash:
|
||||
"""Convert attributes to a hashable key for grouping."""
|
||||
stretch = attr.get("stretch", "")
|
||||
params = attr.get("params", {})
|
||||
return (
|
||||
parse_alignment(attr.get("align", "")),
|
||||
parse_alignment(attr.get("valign", "")),
|
||||
stretch,
|
||||
float(attr.get("params", {}).get("overlap", 0.0)),
|
||||
float(attr.get("params", {}).get("xy-ratio", -1.0)),
|
||||
float(attr.get("params", {}).get("ypadding", 0.0)),
|
||||
attr.get("stretch", ""),
|
||||
float(params.get("overlap", 0.0)),
|
||||
float(params.get("xy-ratio", -1.0)),
|
||||
float(params.get("ypadding", 0.0)),
|
||||
)
|
||||
|
||||
|
||||
def coalesce_codepoints_to_ranges(codepoints):
|
||||
def coalesce_codepoints_to_ranges(codepoints: list[int]) -> list[tuple[int, int]]:
|
||||
"""Convert a sorted list of integers to a list of single values and ranges."""
|
||||
ranges = []
|
||||
ranges: list[tuple[int, int]] = []
|
||||
cp_iter = iter(sorted(codepoints))
|
||||
try:
|
||||
with suppress(StopIteration):
|
||||
start = prev = next(cp_iter)
|
||||
for cp in cp_iter:
|
||||
if cp == prev + 1:
|
||||
@ -135,88 +153,96 @@ def coalesce_codepoints_to_ranges(codepoints):
|
||||
ranges.append((start, prev))
|
||||
start = prev = cp
|
||||
ranges.append((start, prev))
|
||||
except StopIteration:
|
||||
pass
|
||||
return ranges
|
||||
|
||||
|
||||
def emit_zig_entry_multikey(codepoints, attr):
|
||||
def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) -> str:
|
||||
align = parse_alignment(attr.get("align", ""))
|
||||
valign = parse_alignment(attr.get("valign", ""))
|
||||
stretch = attr.get("stretch", "")
|
||||
params = attr.get("params", {})
|
||||
|
||||
overlap = get_param(params, "overlap", 0.0)
|
||||
xy_ratio = get_param(params, "xy-ratio", -1.0)
|
||||
y_padding = get_param(params, "ypadding", 0.0)
|
||||
overlap = params.get("overlap", 0.0)
|
||||
xy_ratio = params.get("xy-ratio", -1.0)
|
||||
y_padding = params.get("ypadding", 0.0)
|
||||
|
||||
ranges = coalesce_codepoints_to_ranges(codepoints)
|
||||
keys = "\n".join(
|
||||
f" 0x{start:x}...0x{end:x}," if start != end else f" 0x{start:x},"
|
||||
f" {start:#x}...{end:#x}," if start != end else f" {start:#x},"
|
||||
for start, end in ranges
|
||||
)
|
||||
|
||||
s = f"""{keys}
|
||||
=> .{{\n"""
|
||||
s = f"{keys}\n => .{{\n"
|
||||
|
||||
# These translations don't quite capture the way
|
||||
# the actual patcher does scaling, but they're a
|
||||
# good enough compromise.
|
||||
if ("xy" in stretch):
|
||||
if "xy" in stretch:
|
||||
s += " .size_horizontal = .stretch,\n"
|
||||
s += " .size_vertical = .stretch,\n"
|
||||
elif ("!" in stretch):
|
||||
elif "!" in stretch:
|
||||
s += " .size_horizontal = .cover,\n"
|
||||
s += " .size_vertical = .fit,\n"
|
||||
elif ("^" in stretch):
|
||||
elif "^" in stretch:
|
||||
s += " .size_horizontal = .cover,\n"
|
||||
s += " .size_vertical = .cover,\n"
|
||||
else:
|
||||
s += " .size_horizontal = .fit,\n"
|
||||
s += " .size_vertical = .fit,\n"
|
||||
|
||||
if (align is not None):
|
||||
# There are two cases where we want to limit the constraint width to 1:
|
||||
# - If there's a `1` in the stretch mode string.
|
||||
# - If the stretch mode is `xy` and there's not an explicit `2`.
|
||||
if "1" in stretch or ("xy" in stretch and "2" not in stretch):
|
||||
s += " .max_constraint_width = 1,\n"
|
||||
|
||||
if align is not None:
|
||||
s += f" .align_horizontal = {align},\n"
|
||||
if (valign is not None):
|
||||
if valign is not None:
|
||||
s += f" .align_vertical = {valign},\n"
|
||||
|
||||
if (overlap != 0.0):
|
||||
# `overlap` and `ypadding` are mutually exclusive,
|
||||
# this is asserted in the nerd fonts patcher itself.
|
||||
if overlap:
|
||||
pad = -overlap
|
||||
s += f" .pad_left = {pad},\n"
|
||||
s += f" .pad_right = {pad},\n"
|
||||
v_pad = y_padding - math.copysign(min(0.01, abs(overlap)), overlap)
|
||||
# In the nerd fonts patcher, overlap values
|
||||
# are capped at 0.01 in the vertical direction.
|
||||
v_pad = -min(0.01, overlap)
|
||||
s += f" .pad_top = {v_pad},\n"
|
||||
s += f" .pad_bottom = {v_pad},\n"
|
||||
elif y_padding:
|
||||
s += f" .pad_top = {y_padding / 2},\n"
|
||||
s += f" .pad_bottom = {y_padding / 2},\n"
|
||||
|
||||
if (xy_ratio > 0):
|
||||
if xy_ratio > 0:
|
||||
s += f" .max_xy_ratio = {xy_ratio},\n"
|
||||
|
||||
s += " },"
|
||||
|
||||
return s
|
||||
|
||||
def generate_zig_switch_arms(patch_set):
|
||||
entries = {}
|
||||
for entry in patch_set:
|
||||
|
||||
def generate_zig_switch_arms(patch_sets: list[PatchSet]) -> str:
|
||||
entries: dict[int, PatchSetAttributeEntry] = {}
|
||||
for entry in patch_sets:
|
||||
attributes = entry["Attributes"]
|
||||
|
||||
for cp in range(entry["SymStart"], entry["SymEnd"] + 1):
|
||||
entries[cp] = attributes["default"]
|
||||
|
||||
for k, v in attributes.items():
|
||||
if isinstance(k, int):
|
||||
entries[k] = v
|
||||
entries |= {k: v for k, v in attributes.items() if isinstance(k, int)}
|
||||
|
||||
del entries[0]
|
||||
|
||||
# Group codepoints by attribute key
|
||||
grouped = defaultdict(list)
|
||||
grouped = defaultdict[AttributeHash, list[int]](list)
|
||||
for cp, attr in entries.items():
|
||||
grouped[attr_key(attr)].append(cp)
|
||||
|
||||
# Emit zig switch arms
|
||||
result = []
|
||||
for _, codepoints in sorted(grouped.items(), key=lambda x: x[1]):
|
||||
result: list[str] = []
|
||||
for codepoints in sorted(grouped.values()):
|
||||
# Use one of the attrs in the group to emit the value
|
||||
attr = entries[codepoints[0]]
|
||||
result.append(emit_zig_entry_multikey(codepoints, attr))
|
||||
@ -225,23 +251,16 @@ def generate_zig_switch_arms(patch_set):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
path = (
|
||||
Path(__file__).resolve().parent
|
||||
/ ".."
|
||||
/ ".."
|
||||
/ "vendor"
|
||||
/ "nerd-fonts"
|
||||
/ "font-patcher.py"
|
||||
)
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
source = f.read()
|
||||
project_root = Path(__file__).resolve().parents[2]
|
||||
|
||||
patcher_path = project_root / "vendor" / "nerd-fonts" / "font-patcher.py"
|
||||
source = patcher_path.read_text(encoding="utf-8")
|
||||
patch_set = extract_patch_set_values(source)
|
||||
|
||||
out_path = Path(__file__).resolve().parent / "nerd_font_attributes.zig"
|
||||
out_path = project_root / "src" / "font" / "nerd_font_attributes.zig"
|
||||
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write("""//! This is a generate file, produced by nerd_font_codegen.py
|
||||
with out_path.open("w", encoding="utf-8") as f:
|
||||
f.write("""//! This is a generated file, produced by nerd_font_codegen.py
|
||||
//! DO NOT EDIT BY HAND!
|
||||
//!
|
||||
//! This file provides info extracted from the nerd fonts patcher script,
|
||||
@ -254,6 +273,4 @@ pub fn getConstraint(cp: u21) Constraint {
|
||||
return switch (cp) {
|
||||
""")
|
||||
f.write(generate_zig_switch_arms(patch_set))
|
||||
f.write("\n")
|
||||
|
||||
f.write(" else => .none,\n };\n}\n")
|
||||
f.write("\n else => .none,\n };\n}\n")
|
||||
|
@ -195,7 +195,6 @@ pub fn renderGlyph(
|
||||
.offset_y = 0,
|
||||
.atlas_x = 0,
|
||||
.atlas_y = 0,
|
||||
.advance_x = 0,
|
||||
};
|
||||
|
||||
const metrics = self.metrics;
|
||||
@ -227,8 +226,6 @@ pub fn renderGlyph(
|
||||
.offset_y = @as(i32, @intCast(region.height +| canvas.clip_bottom)) - @as(i32, @intCast(padding_y)),
|
||||
.atlas_x = region.x,
|
||||
.atlas_y = region.y,
|
||||
.advance_x = @floatFromInt(width),
|
||||
.sprite = true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -140,24 +140,7 @@ pub const Canvas = struct {
|
||||
const region_height = sfc_height -| self.clip_top -| self.clip_bottom;
|
||||
|
||||
// Allocate our texture atlas region
|
||||
const region = region: {
|
||||
// Reserve a region with a 1px margin on the bottom and right edges
|
||||
// so that we can avoid interpolation between adjacent glyphs during
|
||||
// texture sampling.
|
||||
var region = try atlas.reserve(
|
||||
alloc,
|
||||
region_width + 1,
|
||||
region_height + 1,
|
||||
);
|
||||
|
||||
// Modify the region to remove the margin so that we write to the
|
||||
// non-zero location. The data in an Altlas is always initialized
|
||||
// to zero (Atlas.clear) so we don't need to worry about zero-ing
|
||||
// that.
|
||||
region.width -= 1;
|
||||
region.height -= 1;
|
||||
break :region region;
|
||||
};
|
||||
const region = try atlas.reserve(alloc, region_width, region_height);
|
||||
|
||||
if (region.width > 0 and region.height > 0) {
|
||||
const buffer: []u8 = @ptrCast(self.sfc.image_surface_alpha8.buf);
|
||||
|
@ -281,6 +281,10 @@ pub const Action = union(enum) {
|
||||
/// If there is a URL under the cursor, copy it to the default clipboard.
|
||||
copy_url_to_clipboard,
|
||||
|
||||
/// Copy the terminal title to the clipboard. If the terminal title is not
|
||||
/// set or is empty this has no effect.
|
||||
copy_title_to_clipboard,
|
||||
|
||||
/// Increase the font size by the specified amount in points (pt).
|
||||
///
|
||||
/// For example, `increase_font_size:1.5` will increase the font size
|
||||
@ -296,6 +300,12 @@ pub const Action = union(enum) {
|
||||
/// Reset the font size to the original configured size.
|
||||
reset_font_size,
|
||||
|
||||
/// Set the font size to the specified size in points (pt).
|
||||
///
|
||||
/// For example, `set_font_size:14.5` will set the font size
|
||||
/// to 14.5 points.
|
||||
set_font_size: f32,
|
||||
|
||||
/// Clear the screen and all scrollback.
|
||||
clear_screen,
|
||||
|
||||
@ -999,11 +1009,13 @@ pub const Action = union(enum) {
|
||||
.reset,
|
||||
.copy_to_clipboard,
|
||||
.copy_url_to_clipboard,
|
||||
.copy_title_to_clipboard,
|
||||
.paste_from_clipboard,
|
||||
.paste_from_selection,
|
||||
.increase_font_size,
|
||||
.decrease_font_size,
|
||||
.reset_font_size,
|
||||
.set_font_size,
|
||||
.prompt_surface_title,
|
||||
.clear_screen,
|
||||
.select_all,
|
||||
@ -3065,6 +3077,7 @@ test "set: getEvent codepoint case folding" {
|
||||
try testing.expect(action == null);
|
||||
}
|
||||
}
|
||||
|
||||
test "Action: clone" {
|
||||
const testing = std.testing;
|
||||
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||
@ -3083,3 +3096,42 @@ test "Action: clone" {
|
||||
try testing.expect(b == .text);
|
||||
}
|
||||
}
|
||||
|
||||
test "parse: increase_font_size" {
|
||||
const testing = std.testing;
|
||||
|
||||
{
|
||||
const binding = try parseSingle("a=increase_font_size:1.5");
|
||||
try testing.expect(binding.action == .increase_font_size);
|
||||
try testing.expectEqual(1.5, binding.action.increase_font_size);
|
||||
}
|
||||
}
|
||||
|
||||
test "parse: decrease_font_size" {
|
||||
const testing = std.testing;
|
||||
|
||||
{
|
||||
const binding = try parseSingle("a=decrease_font_size:2.5");
|
||||
try testing.expect(binding.action == .decrease_font_size);
|
||||
try testing.expectEqual(2.5, binding.action.decrease_font_size);
|
||||
}
|
||||
}
|
||||
|
||||
test "parse: reset_font_size" {
|
||||
const testing = std.testing;
|
||||
|
||||
{
|
||||
const binding = try parseSingle("a=reset_font_size");
|
||||
try testing.expect(binding.action == .reset_font_size);
|
||||
}
|
||||
}
|
||||
|
||||
test "parse: set_font_size" {
|
||||
const testing = std.testing;
|
||||
|
||||
{
|
||||
const binding = try parseSingle("a=set_font_size:13.5");
|
||||
try testing.expect(binding.action == .set_font_size);
|
||||
try testing.expectEqual(13.5, binding.action.set_font_size);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Action = @import("Binding.zig").Action;
|
||||
@ -131,6 +132,12 @@ fn actionCommands(action: Action.Key) []const Command {
|
||||
.description = "Copy the URL under the cursor to the clipboard.",
|
||||
}},
|
||||
|
||||
.copy_title_to_clipboard => comptime &.{.{
|
||||
.action = .copy_title_to_clipboard,
|
||||
.title = "Copy Terminal Title to Clipboard",
|
||||
.description = "Copy the terminal title to the clipboard. If the terminal title is not set this has no effect.",
|
||||
}},
|
||||
|
||||
.paste_from_clipboard => comptime &.{.{
|
||||
.action = .paste_from_clipboard,
|
||||
.title = "Paste from Clipboard",
|
||||
@ -460,6 +467,7 @@ fn actionCommands(action: Action.Key) []const Command {
|
||||
.esc,
|
||||
.text,
|
||||
.cursor_key,
|
||||
.set_font_size,
|
||||
.scroll_page_fractional,
|
||||
.scroll_page_lines,
|
||||
.adjust_selection,
|
||||
|
@ -19,7 +19,12 @@ const internal_os = @import("os/main.zig");
|
||||
|
||||
// Some comptime assertions that our C API depends on.
|
||||
comptime {
|
||||
assert(apprt.runtime == apprt.embedded);
|
||||
// We allow tests to reference this file because we unit test
|
||||
// some of the C API. At runtime though we should never get these
|
||||
// functions unless we are building libghostty.
|
||||
if (!builtin.is_test) {
|
||||
assert(apprt.runtime == apprt.embedded);
|
||||
}
|
||||
}
|
||||
|
||||
/// Global options so we can log. This is identical to main.
|
||||
@ -29,7 +34,9 @@ comptime {
|
||||
// These structs need to be referenced so the `export` functions
|
||||
// are truly exported by the C API lib.
|
||||
_ = @import("config.zig").CAPI;
|
||||
_ = apprt.runtime.CAPI;
|
||||
if (@hasDecl(apprt.runtime, "CAPI")) {
|
||||
_ = apprt.runtime.CAPI;
|
||||
}
|
||||
}
|
||||
|
||||
/// ghostty_info_s
|
||||
@ -46,17 +53,29 @@ const Info = extern struct {
|
||||
};
|
||||
};
|
||||
|
||||
/// Initialize ghostty global state. It is possible to have more than
|
||||
/// one global state but it has zero practical benefit.
|
||||
export fn ghostty_init() c_int {
|
||||
/// ghostty_string_s
|
||||
pub const String = extern struct {
|
||||
ptr: ?[*]const u8,
|
||||
len: usize,
|
||||
|
||||
pub const empty: String = .{
|
||||
.ptr = null,
|
||||
.len = 0,
|
||||
};
|
||||
|
||||
pub fn fromSlice(slice: []const u8) String {
|
||||
return .{
|
||||
.ptr = slice.ptr,
|
||||
.len = slice.len,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Initialize ghostty global state.
|
||||
export fn ghostty_init(argc: usize, argv: [*][*:0]u8) c_int {
|
||||
assert(builtin.link_libc);
|
||||
|
||||
// Since in the lib we don't go through start.zig, we need
|
||||
// to populate argv so that inspecting std.os.argv doesn't
|
||||
// touch uninitialized memory.
|
||||
var argv: [0][*:0]u8 = .{};
|
||||
std.os.argv = &argv;
|
||||
|
||||
std.os.argv = argv[0..argc];
|
||||
state.init() catch |err| {
|
||||
std.log.err("failed to initialize ghostty error={}", .{err});
|
||||
return 1;
|
||||
@ -65,15 +84,17 @@ export fn ghostty_init() c_int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// This is the entrypoint for the CLI version of Ghostty. This
|
||||
/// is mutually exclusive to ghostty_init. Do NOT run ghostty_init
|
||||
/// if you are going to run this. This will not return.
|
||||
export fn ghostty_cli_main(argc: usize, argv: [*][*:0]u8) noreturn {
|
||||
std.os.argv = argv[0..argc];
|
||||
main.main() catch |err| {
|
||||
std.log.err("failed to run ghostty error={}", .{err});
|
||||
/// Runs an action if it is specified. If there is no action this returns
|
||||
/// false. If there is an action then this doesn't return.
|
||||
export fn ghostty_cli_try_action() void {
|
||||
const action = state.action orelse return;
|
||||
std.log.info("executing CLI action={}", .{action});
|
||||
posix.exit(action.run(state.alloc) catch |err| {
|
||||
std.log.err("CLI action failed error={}", .{err});
|
||||
posix.exit(1);
|
||||
};
|
||||
});
|
||||
|
||||
posix.exit(0);
|
||||
}
|
||||
|
||||
/// Return metadata about Ghostty, such as version, build mode, etc.
|
||||
@ -99,3 +120,8 @@ export fn ghostty_info() Info {
|
||||
export fn ghostty_translate(msgid: [*:0]const u8) [*:0]const u8 {
|
||||
return internal_os.i18n._(msgid);
|
||||
}
|
||||
|
||||
/// Free a string allocated by Ghostty.
|
||||
export fn ghostty_string_free(str: String) void {
|
||||
state.alloc.free(str.ptr.?[0..str.len]);
|
||||
}
|
||||
|
@ -24,8 +24,15 @@ pub fn launchedFromDesktop() bool {
|
||||
// This special case is so that if we launch the app via the
|
||||
// app bundle (i.e. via open) then we still treat it as if it
|
||||
// was launched from the desktop.
|
||||
if (build_config.artifact == .lib and
|
||||
posix.getenv("GHOSTTY_MAC_APP") != null) break :macos true;
|
||||
if (build_config.artifact == .lib) lib: {
|
||||
const env = "GHOSTTY_MAC_LAUNCH_SOURCE";
|
||||
const source = posix.getenv(env) orelse break :lib;
|
||||
|
||||
// Source can be "app", "cli", or "zig_run". We assume
|
||||
// its the desktop only if its "app". We may want to do
|
||||
// "zig_run" but at the moment there's no reason.
|
||||
if (std.mem.eql(u8, source, "app")) break :macos true;
|
||||
}
|
||||
|
||||
break :macos c.getppid() == 1;
|
||||
},
|
||||
|
@ -49,6 +49,7 @@ pub const locales = [_][:0]const u8{
|
||||
"ca_ES.UTF-8",
|
||||
"bg_BG.UTF-8",
|
||||
"ga_IE.UTF-8",
|
||||
"he_IL.UTF-8",
|
||||
};
|
||||
|
||||
/// Set for faster membership lookup of locales.
|
||||
|
27
src/os/kernel_info.zig
Normal file
27
src/os/kernel_info.zig
Normal file
@ -0,0 +1,27 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub fn getKernelInfo(alloc: std.mem.Allocator) ?[]const u8 {
|
||||
if (comptime builtin.os.tag != .linux) return null;
|
||||
const path = "/proc/sys/kernel/osrelease";
|
||||
var file = std.fs.openFileAbsolute(path, .{}) catch return null;
|
||||
defer file.close();
|
||||
|
||||
// 128 bytes should be enough to hold the kernel information
|
||||
const kernel_info = file.readToEndAlloc(alloc, 128) catch return null;
|
||||
defer alloc.free(kernel_info);
|
||||
return alloc.dupe(u8, std.mem.trim(u8, kernel_info, &std.ascii.whitespace)) catch return null;
|
||||
}
|
||||
|
||||
test "read /proc/sys/kernel/osrelease" {
|
||||
if (comptime builtin.os.tag != .linux) return null;
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
const kernel_info = try getKernelInfo(allocator);
|
||||
defer allocator.free(kernel_info);
|
||||
|
||||
// Since we can't hardcode the info in tests, just check
|
||||
// if something was read from the file
|
||||
try std.testing.expect(kernel_info.len > 0);
|
||||
try std.testing.expect(!std.mem.eql(u8, kernel_info, ""));
|
||||
}
|
@ -14,6 +14,7 @@ const openpkg = @import("open.zig");
|
||||
const pipepkg = @import("pipe.zig");
|
||||
const resourcesdir = @import("resourcesdir.zig");
|
||||
const systemd = @import("systemd.zig");
|
||||
const kernelInfo = @import("kernel_info.zig");
|
||||
|
||||
// Namespaces
|
||||
pub const args = @import("args.zig");
|
||||
@ -58,6 +59,7 @@ pub const pipe = pipepkg.pipe;
|
||||
pub const resourcesDir = resourcesdir.resourcesDir;
|
||||
pub const ResourcesDir = resourcesdir.ResourcesDir;
|
||||
pub const ShellEscapeWriter = shell.ShellEscapeWriter;
|
||||
pub const getKernelInfo = kernelInfo.getKernelInfo;
|
||||
|
||||
test {
|
||||
_ = i18n;
|
||||
|
@ -1,24 +1,23 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const apprt = @import("../apprt.zig");
|
||||
|
||||
const log = std.log.scoped(.@"os-open");
|
||||
|
||||
/// The type of the data at the URL to open. This is used as a hint
|
||||
/// to potentially open the URL in a different way.
|
||||
pub const Type = enum {
|
||||
text,
|
||||
unknown,
|
||||
};
|
||||
|
||||
/// Open a URL in the default handling application.
|
||||
///
|
||||
/// Any output on stderr is logged as a warning in the application logs.
|
||||
/// Output on stdout is ignored. The allocator is used to buffer the
|
||||
/// log output and may allocate from another thread.
|
||||
///
|
||||
/// This function is purposely simple for the sake of providing
|
||||
/// some portable way to open URLs. If you are implementing an
|
||||
/// apprt for Ghostty, you should consider doing something special-cased
|
||||
/// for your platform.
|
||||
pub fn open(
|
||||
alloc: Allocator,
|
||||
typ: Type,
|
||||
kind: apprt.action.OpenUrl.Kind,
|
||||
url: []const u8,
|
||||
) !void {
|
||||
var exe: std.process.Child = switch (builtin.os.tag) {
|
||||
@ -33,7 +32,7 @@ pub fn open(
|
||||
),
|
||||
|
||||
.macos => .init(
|
||||
switch (typ) {
|
||||
switch (kind) {
|
||||
.text => &.{ "open", "-t", url },
|
||||
.unknown => &.{ "open", url },
|
||||
},
|
||||
|
@ -356,6 +356,10 @@ pub inline fn textureOptions(self: OpenGL) Texture.Options {
|
||||
.format = .rgba,
|
||||
.internal_format = .srgba,
|
||||
.target = .@"2D",
|
||||
.min_filter = .linear,
|
||||
.mag_filter = .linear,
|
||||
.wrap_s = .clamp_to_edge,
|
||||
.wrap_t = .clamp_to_edge,
|
||||
};
|
||||
}
|
||||
|
||||
@ -388,6 +392,16 @@ pub inline fn imageTextureOptions(
|
||||
.format = format.toPixelFormat(),
|
||||
.internal_format = if (srgb) .srgba else .rgba,
|
||||
.target = .@"2D",
|
||||
// TODO: Generate mipmaps for image textures and use
|
||||
// linear_mipmap_linear filtering so that they
|
||||
// look good even when scaled way down.
|
||||
.min_filter = .linear,
|
||||
.mag_filter = .linear,
|
||||
// TODO: Separate out background image options, use
|
||||
// repeating coordinate modes so we don't have
|
||||
// to do the modulus in the shader.
|
||||
.wrap_s = .clamp_to_edge,
|
||||
.wrap_t = .clamp_to_edge,
|
||||
};
|
||||
}
|
||||
|
||||
@ -409,6 +423,10 @@ pub fn initAtlasTexture(
|
||||
.format = format,
|
||||
.internal_format = internal_format,
|
||||
.target = .Rectangle,
|
||||
.min_filter = .nearest,
|
||||
.mag_filter = .nearest,
|
||||
.wrap_s = .clamp_to_edge,
|
||||
.wrap_t = .clamp_to_edge,
|
||||
},
|
||||
atlas.size,
|
||||
atlas.size,
|
||||
|
@ -519,7 +519,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
foreground: terminal.color.RGB,
|
||||
selection_background: ?configpkg.Config.TerminalColor,
|
||||
selection_foreground: ?configpkg.Config.TerminalColor,
|
||||
bold_is_bright: bool,
|
||||
bold_color: ?configpkg.BoldColor,
|
||||
min_contrast: f32,
|
||||
padding_color: configpkg.WindowPaddingColor,
|
||||
custom_shaders: configpkg.RepeatablePath,
|
||||
@ -580,7 +580,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
|
||||
.background = config.background.toTerminalRGB(),
|
||||
.foreground = config.foreground.toTerminalRGB(),
|
||||
.bold_is_bright = config.@"bold-is-bright",
|
||||
.bold_color = config.@"bold-color",
|
||||
|
||||
.min_contrast = @floatCast(config.@"minimum-contrast"),
|
||||
.padding_color = config.@"window-padding-color",
|
||||
|
||||
@ -2540,10 +2541,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
// the cell style (SGR), before applying any additional
|
||||
// configuration, inversions, selections, etc.
|
||||
const bg_style = style.bg(cell, color_palette);
|
||||
const fg_style = style.fg(
|
||||
color_palette,
|
||||
self.config.bold_is_bright,
|
||||
) orelse self.foreground_color orelse self.default_foreground_color;
|
||||
const fg_style = style.fg(.{
|
||||
.default = self.foreground_color orelse self.default_foreground_color,
|
||||
.palette = color_palette,
|
||||
.bold = self.config.bold_color,
|
||||
});
|
||||
|
||||
// The final background color for the cell.
|
||||
const bg = bg: {
|
||||
@ -2801,10 +2803,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
.@"cell-background",
|
||||
=> |_, tag| {
|
||||
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
|
||||
const fg_style = sty.fg(
|
||||
color_palette,
|
||||
self.config.bold_is_bright,
|
||||
) orelse self.foreground_color orelse self.default_foreground_color;
|
||||
const fg_style = sty.fg(.{
|
||||
.default = self.foreground_color orelse self.default_foreground_color,
|
||||
.palette = color_palette,
|
||||
.bold = self.config.bold_color,
|
||||
});
|
||||
const bg_style = sty.bg(
|
||||
screen.cursor.page_cell,
|
||||
color_palette,
|
||||
@ -2852,7 +2855,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
}
|
||||
|
||||
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
|
||||
const fg_style = sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color;
|
||||
const fg_style = sty.fg(.{
|
||||
.default = self.foreground_color orelse self.default_foreground_color,
|
||||
.palette = color_palette,
|
||||
.bold = self.config.bold_color,
|
||||
});
|
||||
const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color;
|
||||
|
||||
break :blk switch (txt) {
|
||||
|
@ -16,6 +16,10 @@ pub const Options = struct {
|
||||
format: gl.Texture.Format,
|
||||
internal_format: gl.Texture.InternalFormat,
|
||||
target: gl.Texture.Target,
|
||||
min_filter: gl.Texture.MinFilter,
|
||||
mag_filter: gl.Texture.MagFilter,
|
||||
wrap_s: gl.Texture.Wrap,
|
||||
wrap_t: gl.Texture.Wrap,
|
||||
};
|
||||
|
||||
texture: gl.Texture,
|
||||
@ -48,10 +52,10 @@ pub fn init(
|
||||
{
|
||||
const texbind = tex.bind(opts.target) catch return error.OpenGLFailed;
|
||||
defer texbind.unbind();
|
||||
texbind.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE) catch return error.OpenGLFailed;
|
||||
texbind.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE) catch return error.OpenGLFailed;
|
||||
texbind.parameter(.MinFilter, gl.c.GL_LINEAR) catch return error.OpenGLFailed;
|
||||
texbind.parameter(.MagFilter, gl.c.GL_LINEAR) catch return error.OpenGLFailed;
|
||||
texbind.parameter(.WrapS, @intFromEnum(opts.wrap_s)) catch return error.OpenGLFailed;
|
||||
texbind.parameter(.WrapT, @intFromEnum(opts.wrap_t)) catch return error.OpenGLFailed;
|
||||
texbind.parameter(.MinFilter, @intFromEnum(opts.min_filter)) catch return error.OpenGLFailed;
|
||||
texbind.parameter(.MagFilter, @intFromEnum(opts.mag_filter)) catch return error.OpenGLFailed;
|
||||
texbind.image2D(
|
||||
0,
|
||||
opts.internal_format,
|
||||
|
@ -1,5 +1,6 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const configpkg = @import("../config.zig");
|
||||
const color = @import("color.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const page = @import("page.zig");
|
||||
@ -115,24 +116,68 @@ pub const Style = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the fg color for a cell with this style given the palette.
|
||||
pub const Fg = struct {
|
||||
/// The default color to use if the style doesn't specify a
|
||||
/// foreground color and no configuration options override
|
||||
/// it.
|
||||
default: color.RGB,
|
||||
|
||||
/// The current color palette. Required to map palette indices to
|
||||
/// real color values.
|
||||
palette: *const color.Palette,
|
||||
|
||||
/// If specified, the color to use for bold text.
|
||||
bold: ?configpkg.BoldColor = null,
|
||||
};
|
||||
|
||||
/// Returns the fg color for a cell with this style given the palette
|
||||
/// and various configuration options.
|
||||
pub fn fg(
|
||||
self: Style,
|
||||
palette: *const color.Palette,
|
||||
bold_is_bright: bool,
|
||||
) ?color.RGB {
|
||||
opts: Fg,
|
||||
) color.RGB {
|
||||
// Note we don't pull the bold check to the top-level here because
|
||||
// we don't want to duplicate the conditional multiple times since
|
||||
// certain colors require more checks (e.g. `bold_is_bright`).
|
||||
|
||||
return switch (self.fg_color) {
|
||||
.none => null,
|
||||
.palette => |idx| palette: {
|
||||
if (bold_is_bright and self.flags.bold) {
|
||||
const bright_offset = @intFromEnum(color.Name.bright_black);
|
||||
if (idx < bright_offset)
|
||||
break :palette palette[idx + bright_offset];
|
||||
.none => default: {
|
||||
if (self.flags.bold) {
|
||||
if (opts.bold) |bold| switch (bold) {
|
||||
.bright => {},
|
||||
.color => |v| break :default v.toTerminalRGB(),
|
||||
};
|
||||
}
|
||||
|
||||
break :palette palette[idx];
|
||||
break :default opts.default;
|
||||
},
|
||||
|
||||
.palette => |idx| palette: {
|
||||
if (self.flags.bold) {
|
||||
if (opts.bold) |bold| switch (bold) {
|
||||
.color => |v| break :palette v.toTerminalRGB(),
|
||||
.bright => {
|
||||
const bright_offset = @intFromEnum(color.Name.bright_black);
|
||||
if (idx < bright_offset) {
|
||||
break :palette opts.palette[idx + bright_offset];
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
break :palette opts.palette[idx];
|
||||
},
|
||||
|
||||
.rgb => |rgb| rgb: {
|
||||
if (self.flags.bold and rgb.eql(opts.default)) {
|
||||
if (opts.bold) |bold| switch (bold) {
|
||||
.color => |v| break :rgb v.toTerminalRGB(),
|
||||
.bright => {},
|
||||
};
|
||||
}
|
||||
|
||||
break :rgb rgb;
|
||||
},
|
||||
.rgb => |rgb| rgb,
|
||||
};
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user