mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 19:45:49 +00:00
Merge and fix conflict on README
This commit is contained in:
4
.github/workflows/nix.yml
vendored
4
.github/workflows/nix.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
/nix
|
||||
/zig
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
|
||||
10
.github/workflows/release-tag.yml
vendored
10
.github/workflows/release-tag.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
/nix
|
||||
/zig
|
||||
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
GHOSTTY_COMMIT: ${{ needs.setup.outputs.commit }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
with:
|
||||
@@ -306,7 +306,7 @@ jobs:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Download macOS Artifacts
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
|
||||
30
.github/workflows/release-tip.yml
vendored
30
.github/workflows/release-tip.yml
vendored
@@ -29,11 +29,11 @@ jobs:
|
||||
commit: ${{ steps.extract_build_info.outputs.commit }}
|
||||
commit_long: ${{ steps.extract_build_info.outputs.commit_long }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
needs: [setup, build-macos]
|
||||
if: needs.setup.outputs.should_skip != 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Tip Tag
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
env:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Install sentry-cli
|
||||
run: |
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
env:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Install sentry-cli
|
||||
run: |
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
env:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Install sentry-cli
|
||||
run: |
|
||||
@@ -159,14 +159,14 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -186,7 +186,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@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
@@ -217,7 +217,7 @@ jobs:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
@@ -356,7 +356,7 @@ jobs:
|
||||
|
||||
# Update Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
@@ -451,7 +451,7 @@ jobs:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
@@ -583,7 +583,7 @@ jobs:
|
||||
|
||||
# Update Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
@@ -635,7 +635,7 @@ jobs:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
@@ -767,7 +767,7 @@ jobs:
|
||||
|
||||
# Update Release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
|
||||
110
.github/workflows/test.yml
vendored
110
.github/workflows/test.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -179,7 +179,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -232,7 +232,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -258,7 +258,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -268,7 +268,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -287,7 +287,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -297,7 +297,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -320,7 +320,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -330,7 +330,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -366,7 +366,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -376,7 +376,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -404,7 +404,7 @@ jobs:
|
||||
needs: [build-dist, build-snap]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Trigger Snap workflow
|
||||
run: |
|
||||
@@ -421,7 +421,7 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
@@ -464,7 +464,7 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
@@ -509,7 +509,7 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
# This could be from a script if we wanted to but inlining here for now
|
||||
# in one place.
|
||||
@@ -580,7 +580,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Get required Zig version
|
||||
id: zig
|
||||
@@ -595,7 +595,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -627,7 +627,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -637,7 +637,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -675,7 +675,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -685,7 +685,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -710,7 +710,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -720,7 +720,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -737,7 +737,7 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
@@ -774,7 +774,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -784,7 +784,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -804,14 +804,14 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -832,14 +832,14 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -859,14 +859,14 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -886,14 +886,14 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -913,14 +913,14 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -940,14 +940,14 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -974,14 +974,14 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -1001,14 +1001,14 @@ jobs:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -1035,7 +1035,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -1045,7 +1045,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -1104,7 +1104,7 @@ jobs:
|
||||
runs-on: ${{ matrix.variant.runner }}
|
||||
needs: test
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@92ae9851ad316786193b1fd3f40c4b51eb5cb101 # v6.6
|
||||
with:
|
||||
bundle: com.mitchellh.ghostty
|
||||
@@ -1123,7 +1123,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@446d8f390563cd54ca27e8de5bdb816f63c0b706 # v1.2.21
|
||||
@@ -1133,7 +1133,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -1162,7 +1162,7 @@ jobs:
|
||||
# timeout-minutes: 10
|
||||
# steps:
|
||||
# - name: Checkout Ghostty
|
||||
# uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
# uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
#
|
||||
# - name: Start SSH
|
||||
# run: |
|
||||
|
||||
6
.github/workflows/update-colorschemes.yml
vendored
6
.github/workflows/update-colorschemes.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
/zig
|
||||
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
run: nix build .#ghostty
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
|
||||
with:
|
||||
title: Update iTerm2 colorschemes
|
||||
base: main
|
||||
|
||||
@@ -55,7 +55,7 @@ pub fn build(b: *std.Build) !void {
|
||||
);
|
||||
|
||||
// Ghostty resources like terminfo, shell integration, themes, etc.
|
||||
const resources = try buildpkg.GhosttyResources.init(b, &config);
|
||||
const resources = try buildpkg.GhosttyResources.init(b, &config, &deps);
|
||||
const i18n = if (config.i18n) try buildpkg.GhosttyI18n.init(b, &config) else null;
|
||||
|
||||
// Ghostty executable, the actual runnable Ghostty program.
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
.lazy = true,
|
||||
},
|
||||
.uucode = .{
|
||||
// TODO: currently the use-llvm branch because its broken on self-hosted
|
||||
.url = "https://github.com/jacobsandlund/uucode/archive/b309dfb4e25a38201d7b300b201a698e02283862.tar.gz",
|
||||
.hash = "uucode-0.1.0-ZZjBPl_mQwDHQowHOzXwgTPhAqcFekfYpAeUfeHZNCl3",
|
||||
// jacobsandlund/uucode
|
||||
.url = "https://github.com/jacobsandlund/uucode/archive/31655fba3c638229989cc524363ef5e3c7b580c1.tar.gz",
|
||||
.hash = "uucode-0.1.0-ZZjBPicPTQDlG6OClzn2bPu7ICkkkyWrTB6aRsBr-A1E",
|
||||
},
|
||||
.zig_wayland = .{
|
||||
// codeberg ifreund/zig-wayland
|
||||
@@ -116,7 +116,7 @@
|
||||
// Other
|
||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||
.iterm2_themes = .{
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251110-150531-d5f3d53/ghostty-themes.tgz",
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251124-150533-2b326a8/ghostty-themes.tgz",
|
||||
.hash = "N-V-__8AAPZCAwDJ0OsIn2nbr3FMvBw68oiv-hC2pFuY1eLN",
|
||||
.lazy = true,
|
||||
},
|
||||
|
||||
10
build.zig.zon.json
generated
10
build.zig.zon.json
generated
@@ -51,8 +51,8 @@
|
||||
},
|
||||
"N-V-__8AAPZCAwDJ0OsIn2nbr3FMvBw68oiv-hC2pFuY1eLN": {
|
||||
"name": "iterm2_themes",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251110-150531-d5f3d53/ghostty-themes.tgz",
|
||||
"hash": "sha256-VZq3L/cAAu7kLA5oqJYNjAZApoblfBtAzfdKVOuJPQI="
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251124-150533-2b326a8/ghostty-themes.tgz",
|
||||
"hash": "sha256-5mmXW7d9SkesHyIwUBlWmyGtOWf6wu0S6zkHe93FVLM="
|
||||
},
|
||||
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
||||
"name": "jetbrains_mono",
|
||||
@@ -114,10 +114,10 @@
|
||||
"url": "git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732",
|
||||
"hash": "sha256-sHPh+TQSdUGus/QTbj7KSJJkTuNTrK4VNmQDjS30Lf8="
|
||||
},
|
||||
"uucode-0.1.0-ZZjBPl_mQwDHQowHOzXwgTPhAqcFekfYpAeUfeHZNCl3": {
|
||||
"uucode-0.1.0-ZZjBPicPTQDlG6OClzn2bPu7ICkkkyWrTB6aRsBr-A1E": {
|
||||
"name": "uucode",
|
||||
"url": "https://github.com/jacobsandlund/uucode/archive/b309dfb4e25a38201d7b300b201a698e02283862.tar.gz",
|
||||
"hash": "sha256-jvko1MdWr1OG4P58KjdB1JMnWy4EbrO3xIkV8fiQkC0="
|
||||
"url": "https://github.com/jacobsandlund/uucode/archive/31655fba3c638229989cc524363ef5e3c7b580c1.tar.gz",
|
||||
"hash": "sha256-SzpYGhgG4B6Luf8eT35sKLobCxjmwEuo1Twk0jeu9g4="
|
||||
},
|
||||
"vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS": {
|
||||
"name": "vaxis",
|
||||
|
||||
10
build.zig.zon.nix
generated
10
build.zig.zon.nix
generated
@@ -166,8 +166,8 @@ in
|
||||
name = "N-V-__8AAPZCAwDJ0OsIn2nbr3FMvBw68oiv-hC2pFuY1eLN";
|
||||
path = fetchZigArtifact {
|
||||
name = "iterm2_themes";
|
||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251110-150531-d5f3d53/ghostty-themes.tgz";
|
||||
hash = "sha256-VZq3L/cAAu7kLA5oqJYNjAZApoblfBtAzfdKVOuJPQI=";
|
||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251124-150533-2b326a8/ghostty-themes.tgz";
|
||||
hash = "sha256-5mmXW7d9SkesHyIwUBlWmyGtOWf6wu0S6zkHe93FVLM=";
|
||||
};
|
||||
}
|
||||
{
|
||||
@@ -267,11 +267,11 @@ in
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "uucode-0.1.0-ZZjBPl_mQwDHQowHOzXwgTPhAqcFekfYpAeUfeHZNCl3";
|
||||
name = "uucode-0.1.0-ZZjBPicPTQDlG6OClzn2bPu7ICkkkyWrTB6aRsBr-A1E";
|
||||
path = fetchZigArtifact {
|
||||
name = "uucode";
|
||||
url = "https://github.com/jacobsandlund/uucode/archive/b309dfb4e25a38201d7b300b201a698e02283862.tar.gz";
|
||||
hash = "sha256-jvko1MdWr1OG4P58KjdB1JMnWy4EbrO3xIkV8fiQkC0=";
|
||||
url = "https://github.com/jacobsandlund/uucode/archive/31655fba3c638229989cc524363ef5e3c7b580c1.tar.gz";
|
||||
hash = "sha256-SzpYGhgG4B6Luf8eT35sKLobCxjmwEuo1Twk0jeu9g4=";
|
||||
};
|
||||
}
|
||||
{
|
||||
|
||||
4
build.zig.zon.txt
generated
4
build.zig.zon.txt
generated
@@ -28,8 +28,8 @@ https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae
|
||||
https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz
|
||||
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
||||
https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz
|
||||
https://github.com/jacobsandlund/uucode/archive/b309dfb4e25a38201d7b300b201a698e02283862.tar.gz
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251110-150531-d5f3d53/ghostty-themes.tgz
|
||||
https://github.com/jacobsandlund/uucode/archive/31655fba3c638229989cc524363ef5e3c7b580c1.tar.gz
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251124-150533-2b326a8/ghostty-themes.tgz
|
||||
https://github.com/natecraddock/zf/archive/3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz
|
||||
https://github.com/rockorager/libvaxis/archive/7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz
|
||||
https://github.com/vancluever/z2d/archive/refs/tags/v0.9.0.tar.gz
|
||||
|
||||
@@ -61,9 +61,9 @@
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251110-150531-d5f3d53/ghostty-themes.tgz",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251124-150533-2b326a8/ghostty-themes.tgz",
|
||||
"dest": "vendor/p/N-V-__8AAPZCAwDJ0OsIn2nbr3FMvBw68oiv-hC2pFuY1eLN",
|
||||
"sha256": "559ab72ff70002eee42c0e68a8960d8c0640a686e57c1b40cdf74a54eb893d02"
|
||||
"sha256": "e669975bb77d4a47ac1f22305019569b21ad3967fac2ed12eb39077bddc554b3"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
@@ -139,9 +139,9 @@
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/jacobsandlund/uucode/archive/b309dfb4e25a38201d7b300b201a698e02283862.tar.gz",
|
||||
"dest": "vendor/p/uucode-0.1.0-ZZjBPl_mQwDHQowHOzXwgTPhAqcFekfYpAeUfeHZNCl3",
|
||||
"sha256": "8ef928d4c756af5386e0fe7c2a3741d493275b2e046eb3b7c48915f1f890902d"
|
||||
"url": "https://github.com/jacobsandlund/uucode/archive/31655fba3c638229989cc524363ef5e3c7b580c1.tar.gz",
|
||||
"dest": "vendor/p/uucode-0.1.0-ZZjBPicPTQDlG6OClzn2bPu7ICkkkyWrTB6aRsBr-A1E",
|
||||
"sha256": "4b3a581a1806e01e8bb9ff1e4f7e6c28ba1b0b18e6c04ba8d53c24d237aef60e"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
|
||||
@@ -747,6 +747,21 @@ typedef struct {
|
||||
uint64_t duration;
|
||||
} ghostty_action_command_finished_s;
|
||||
|
||||
// apprt.action.StartSearch.C
|
||||
typedef struct {
|
||||
const char* needle;
|
||||
} ghostty_action_start_search_s;
|
||||
|
||||
// apprt.action.SearchTotal
|
||||
typedef struct {
|
||||
ssize_t total;
|
||||
} ghostty_action_search_total_s;
|
||||
|
||||
// apprt.action.SearchSelected
|
||||
typedef struct {
|
||||
ssize_t selected;
|
||||
} ghostty_action_search_selected_s;
|
||||
|
||||
// terminal.Scrollbar
|
||||
typedef struct {
|
||||
uint64_t total;
|
||||
@@ -811,6 +826,10 @@ typedef enum {
|
||||
GHOSTTY_ACTION_PROGRESS_REPORT,
|
||||
GHOSTTY_ACTION_SHOW_ON_SCREEN_KEYBOARD,
|
||||
GHOSTTY_ACTION_COMMAND_FINISHED,
|
||||
GHOSTTY_ACTION_START_SEARCH,
|
||||
GHOSTTY_ACTION_END_SEARCH,
|
||||
GHOSTTY_ACTION_SEARCH_TOTAL,
|
||||
GHOSTTY_ACTION_SEARCH_SELECTED,
|
||||
} ghostty_action_tag_e;
|
||||
|
||||
typedef union {
|
||||
@@ -844,6 +863,9 @@ typedef union {
|
||||
ghostty_surface_message_childexited_s child_exited;
|
||||
ghostty_action_progress_report_s progress_report;
|
||||
ghostty_action_command_finished_s command_finished;
|
||||
ghostty_action_start_search_s start_search;
|
||||
ghostty_action_search_total_s search_total;
|
||||
ghostty_action_search_selected_s search_selected;
|
||||
} ghostty_action_u;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -44,6 +44,11 @@ class AppDelegate: NSObject,
|
||||
@IBOutlet private var menuPaste: NSMenuItem?
|
||||
@IBOutlet private var menuPasteSelection: NSMenuItem?
|
||||
@IBOutlet private var menuSelectAll: NSMenuItem?
|
||||
@IBOutlet private var menuFindParent: NSMenuItem?
|
||||
@IBOutlet private var menuFind: NSMenuItem?
|
||||
@IBOutlet private var menuFindNext: NSMenuItem?
|
||||
@IBOutlet private var menuFindPrevious: NSMenuItem?
|
||||
@IBOutlet private var menuHideFindBar: NSMenuItem?
|
||||
|
||||
@IBOutlet private var menuToggleVisibility: NSMenuItem?
|
||||
@IBOutlet private var menuToggleFullScreen: NSMenuItem?
|
||||
@@ -553,6 +558,7 @@ class AppDelegate: NSObject,
|
||||
self.menuMoveSplitDividerLeft?.setImageIfDesired(systemSymbolName: "arrow.left.to.line")
|
||||
self.menuMoveSplitDividerRight?.setImageIfDesired(systemSymbolName: "arrow.right.to.line")
|
||||
self.menuFloatOnTop?.setImageIfDesired(systemSymbolName: "square.filled.on.square")
|
||||
self.menuFindParent?.setImageIfDesired(systemSymbolName: "text.page.badge.magnifyingglass")
|
||||
}
|
||||
|
||||
/// Sync all of our menu item keyboard shortcuts with the Ghostty configuration.
|
||||
@@ -581,6 +587,9 @@ class AppDelegate: NSObject,
|
||||
syncMenuShortcut(config, action: "paste_from_clipboard", menuItem: self.menuPaste)
|
||||
syncMenuShortcut(config, action: "paste_from_selection", menuItem: self.menuPasteSelection)
|
||||
syncMenuShortcut(config, action: "select_all", menuItem: self.menuSelectAll)
|
||||
syncMenuShortcut(config, action: "start_search", menuItem: self.menuFind)
|
||||
syncMenuShortcut(config, action: "search:next", menuItem: self.menuFindNext)
|
||||
syncMenuShortcut(config, action: "search:previous", menuItem: self.menuFindPrevious)
|
||||
|
||||
syncMenuShortcut(config, action: "toggle_split_zoom", menuItem: self.menuZoomSplit)
|
||||
syncMenuShortcut(config, action: "goto_split:previous", menuItem: self.menuPreviousSplit)
|
||||
@@ -885,12 +894,17 @@ class AppDelegate: NSObject,
|
||||
NSApplication.shared.appearance = .init(ghosttyConfig: config)
|
||||
}
|
||||
|
||||
@concurrent
|
||||
// Using AppIconActor to ensure this work
|
||||
// happens synchronously in the background
|
||||
@AppIconActor
|
||||
private func updateAppIcon(from config: Ghostty.Config) async {
|
||||
var appIcon: NSImage?
|
||||
var appIconName: String? = config.macosIcon.rawValue
|
||||
|
||||
switch (config.macosIcon) {
|
||||
case .official:
|
||||
// Discard saved icon name
|
||||
appIconName = nil
|
||||
break
|
||||
case .blueprint:
|
||||
appIcon = NSImage(named: "BlueprintImage")!
|
||||
@@ -919,10 +933,15 @@ class AppDelegate: NSObject,
|
||||
case .custom:
|
||||
if let userIcon = NSImage(contentsOfFile: config.macosCustomIcon) {
|
||||
appIcon = userIcon
|
||||
appIconName = config.macosCustomIcon
|
||||
} else {
|
||||
appIcon = nil // Revert back to official icon if invalid location
|
||||
appIconName = nil // Discard saved icon name
|
||||
}
|
||||
case .customStyle:
|
||||
// Discard saved icon name
|
||||
// if no valid colours were found
|
||||
appIconName = nil
|
||||
guard let ghostColor = config.macosIconGhostColor else { break }
|
||||
guard let screenColors = config.macosIconScreenColor else { break }
|
||||
guard let icon = ColorizedGhosttyIcon(
|
||||
@@ -931,6 +950,24 @@ class AppDelegate: NSObject,
|
||||
frame: config.macosIconFrame
|
||||
).makeImage() else { break }
|
||||
appIcon = icon
|
||||
let colorStrings = ([ghostColor] + screenColors).compactMap(\.hexString)
|
||||
appIconName = (colorStrings + [config.macosIconFrame.rawValue])
|
||||
.joined(separator: "_")
|
||||
}
|
||||
// Only change the icon if it has actually changed
|
||||
// from the current one
|
||||
guard UserDefaults.standard.string(forKey: "CustomGhosttyIcon") != appIconName else {
|
||||
#if DEBUG
|
||||
if appIcon == nil {
|
||||
await MainActor.run {
|
||||
// Changing the app bundle's icon will corrupt code signing.
|
||||
// We only use the default blueprint icon for the dock,
|
||||
// so developers don't need to clean and re-build every time.
|
||||
NSApplication.shared.applicationIconImage = NSImage(named: "BlueprintImage")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return
|
||||
}
|
||||
// make it immutable, so Swift 6 won't complain
|
||||
let newIcon = appIcon
|
||||
@@ -941,16 +978,9 @@ class AppDelegate: NSObject,
|
||||
|
||||
await MainActor.run {
|
||||
self.appIcon = newIcon
|
||||
#if DEBUG
|
||||
// if no custom icon specified, we use blueprint to distinguish from release app
|
||||
NSApplication.shared.applicationIconImage = newIcon ?? NSImage(named: "BlueprintImage")
|
||||
// Changing the app bundle's icon will corrupt code signing.
|
||||
// We only use the default blueprint icon for the dock,
|
||||
// so developers don't need to clean and re-build every time.
|
||||
#else
|
||||
NSApplication.shared.applicationIconImage = newIcon
|
||||
#endif
|
||||
}
|
||||
UserDefaults.standard.set(appIconName, forKey: "CustomGhosttyIcon")
|
||||
}
|
||||
|
||||
//MARK: - Restorable State
|
||||
@@ -1154,10 +1184,19 @@ class AppDelegate: NSObject,
|
||||
// want to bring back these windows if we remove the toggle.
|
||||
//
|
||||
// We also ignore fullscreen windows because they don't hide anyways.
|
||||
self.hiddenWindows = NSApp.windows.filter {
|
||||
var visibleWindows = [Weak<NSWindow>]()
|
||||
NSApp.windows.filter {
|
||||
$0.isVisible &&
|
||||
!$0.styleMask.contains(.fullScreen)
|
||||
}.map { Weak($0) }
|
||||
}.forEach { window in
|
||||
// We only keep track of selectedWindow if it's in a tabGroup,
|
||||
// so we can keep its selection state when restoring
|
||||
let windowToHide = window.tabGroup?.selectedWindow ?? window
|
||||
if !visibleWindows.contains(where: { $0.value === windowToHide }) {
|
||||
visibleWindows.append(Weak(windowToHide))
|
||||
}
|
||||
}
|
||||
self.hiddenWindows = visibleWindows
|
||||
}
|
||||
|
||||
func restore() {
|
||||
@@ -1229,3 +1268,8 @@ extension AppDelegate: NSMenuItemValidation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@globalActor
|
||||
fileprivate actor AppIconActor: GlobalActor {
|
||||
static let shared = AppIconActor()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24123.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24412" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24123.1"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24412"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
@@ -26,7 +26,12 @@
|
||||
<outlet property="menuCopy" destination="Jqf-pv-Zcu" id="bKd-1C-oy9"/>
|
||||
<outlet property="menuDecreaseFontSize" destination="kzb-SZ-dOA" id="Y1B-Vh-6Z2"/>
|
||||
<outlet property="menuEqualizeSplits" destination="3gH-VD-vL9" id="SiZ-ce-FOF"/>
|
||||
<outlet property="menuFind" destination="nwE-0w-30h" id="idg-Nc-apE"/>
|
||||
<outlet property="menuFindNext" destination="XqU-X8-q32" id="vNh-AH-6gZ"/>
|
||||
<outlet property="menuFindParent" destination="cE3-Bt-FcH" id="2dc-ok-hgH"/>
|
||||
<outlet property="menuFindPrevious" destination="1hd-2Z-wVm" id="sSo-wO-2MW"/>
|
||||
<outlet property="menuFloatOnTop" destination="uRj-7z-1Nh" id="94n-o9-Jol"/>
|
||||
<outlet property="menuHideFindBar" destination="xzC-AG-HAc" id="HCo-o6-VWv"/>
|
||||
<outlet property="menuIncreaseFontSize" destination="CIH-ey-Z6x" id="hkc-9C-80E"/>
|
||||
<outlet property="menuMoveSplitDividerDown" destination="Zj7-2W-fdF" id="997-LL-nlN"/>
|
||||
<outlet property="menuMoveSplitDividerLeft" destination="wSR-ny-j1a" id="HCZ-CI-2ob"/>
|
||||
@@ -245,6 +250,39 @@
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VYS-RG-uZD"/>
|
||||
<menuItem title="Find" id="cE3-Bt-FcH">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Find" id="vPo-Sd-cTP">
|
||||
<items>
|
||||
<menuItem title="Find..." id="nwE-0w-30h">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="find:" target="-1" id="PeY-3u-IxC"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Next" id="XqU-X8-q32">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="findNext:" target="-1" id="Dka-ng-aSs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Previous" id="1hd-2Z-wVm">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="findPrevious:" target="-1" id="Zvs-bs-ZR4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="KlV-2C-wYr"/>
|
||||
<menuItem title="Hide Find Bar" id="xzC-AG-HAc">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="findHide:" target="-1" id="hGP-K9-yN9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="Xbz-ms-irt"/>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
|
||||
@@ -44,6 +44,7 @@ struct CommandPaletteView: View {
|
||||
@State private var query = ""
|
||||
@State private var selectedIndex: UInt?
|
||||
@State private var hoveredOptionID: UUID?
|
||||
@FocusState private var isTextFieldFocused: Bool
|
||||
|
||||
// The options that we should show, taking into account any filtering from
|
||||
// the query.
|
||||
@@ -72,7 +73,7 @@ struct CommandPaletteView: View {
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
CommandPaletteQuery(query: $query) { event in
|
||||
CommandPaletteQuery(query: $query, isTextFieldFocused: _isTextFieldFocused) { event in
|
||||
switch (event) {
|
||||
case .exit:
|
||||
isPresented = false
|
||||
@@ -144,6 +145,28 @@ struct CommandPaletteView: View {
|
||||
.shadow(radius: 32, x: 0, y: 12)
|
||||
.padding()
|
||||
.environment(\.colorScheme, scheme)
|
||||
.onChange(of: isPresented) { newValue in
|
||||
// Reset focus when quickly showing and hiding.
|
||||
// macOS will destroy this view after a while,
|
||||
// so task/onAppear will not be called again.
|
||||
// If you toggle it rather quickly, we reset
|
||||
// it here when dismissing.
|
||||
isTextFieldFocused = newValue
|
||||
if !isPresented {
|
||||
// This is optional, since most of the time
|
||||
// there will be a delay before the next use.
|
||||
// To keep behavior the same as before, we reset it.
|
||||
query = ""
|
||||
}
|
||||
}
|
||||
.task {
|
||||
// Grab focus on the first appearance.
|
||||
// This happens right after onAppear,
|
||||
// so we don’t need to dispatch it again.
|
||||
// Fixes: https://github.com/ghostty-org/ghostty/issues/8497
|
||||
// Also fixes initial focus while animating.
|
||||
isTextFieldFocused = isPresented
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +176,12 @@ fileprivate struct CommandPaletteQuery: View {
|
||||
var onEvent: ((KeyboardEvent) -> Void)? = nil
|
||||
@FocusState private var isTextFieldFocused: Bool
|
||||
|
||||
init(query: Binding<String>, isTextFieldFocused: FocusState<Bool>, onEvent: ((KeyboardEvent) -> Void)? = nil) {
|
||||
_query = query
|
||||
self.onEvent = onEvent
|
||||
_isTextFieldFocused = isTextFieldFocused
|
||||
}
|
||||
|
||||
enum KeyboardEvent {
|
||||
case exit
|
||||
case submit
|
||||
@@ -185,14 +214,6 @@ fileprivate struct CommandPaletteQuery: View {
|
||||
.frame(height: 48)
|
||||
.textFieldStyle(.plain)
|
||||
.focused($isTextFieldFocused)
|
||||
.onAppear {
|
||||
// We want to grab focus on appearance. We have to do this after a tick
|
||||
// on macOS Tahoe otherwise this doesn't work. See:
|
||||
// https://github.com/ghostty-org/ghostty/issues/8497
|
||||
DispatchQueue.main.async {
|
||||
isTextFieldFocused = true
|
||||
}
|
||||
}
|
||||
.onChange(of: isTextFieldFocused) { focused in
|
||||
if !focused {
|
||||
onEvent?(.exit)
|
||||
|
||||
@@ -90,19 +90,19 @@ struct TerminalCommandPaletteView: View {
|
||||
backgroundColor: ghosttyConfig.backgroundColor,
|
||||
options: commandOptions
|
||||
)
|
||||
.transition(
|
||||
.move(edge: .top)
|
||||
.combined(with: .opacity)
|
||||
.animation(.spring(response: 0.4, dampingFraction: 0.8))
|
||||
) // Spring animation
|
||||
.zIndex(1) // Ensure it's on top
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .top)
|
||||
}
|
||||
.transition(
|
||||
.move(edge: .top)
|
||||
.combined(with: .opacity)
|
||||
)
|
||||
}
|
||||
}
|
||||
.animation(.spring(response: 0.4, dampingFraction: 0.8), value: isPresented)
|
||||
.onChange(of: isPresented) { newValue in
|
||||
// When the command palette disappears we need to send focus back to the
|
||||
// surface view we were overlaid on top of. There's probably a better way
|
||||
|
||||
@@ -342,7 +342,10 @@ class QuickTerminalController: BaseTerminalController {
|
||||
// animate out.
|
||||
if surfaceTree.isEmpty,
|
||||
let ghostty_app = ghostty.app {
|
||||
let view = Ghostty.SurfaceView(ghostty_app, baseConfig: nil)
|
||||
var config = Ghostty.SurfaceConfiguration()
|
||||
config.environmentVariables["GHOSTTY_QUICK_TERMINAL"] = "1"
|
||||
|
||||
let view = Ghostty.SurfaceView(ghostty_app, baseConfig: config)
|
||||
surfaceTree = SplitTree(view: view)
|
||||
focusedSurface = view
|
||||
}
|
||||
|
||||
@@ -1112,6 +1112,22 @@ class BaseTerminalController: NSWindowController,
|
||||
@IBAction func toggleCommandPalette(_ sender: Any?) {
|
||||
commandPaletteIsShowing.toggle()
|
||||
}
|
||||
|
||||
@IBAction func find(_ sender: Any) {
|
||||
focusedSurface?.find(sender)
|
||||
}
|
||||
|
||||
@IBAction func findNext(_ sender: Any) {
|
||||
focusedSurface?.findNext(sender)
|
||||
}
|
||||
|
||||
@IBAction func findPrevious(_ sender: Any) {
|
||||
focusedSurface?.findNext(sender)
|
||||
}
|
||||
|
||||
@IBAction func findHide(_ sender: Any) {
|
||||
focusedSurface?.findHide(sender)
|
||||
}
|
||||
|
||||
@objc func resetTerminal(_ sender: Any) {
|
||||
guard let surface = focusedSurface?.surface else { return }
|
||||
@@ -1136,3 +1152,15 @@ class BaseTerminalController: NSWindowController,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BaseTerminalController: NSMenuItemValidation {
|
||||
func validateMenuItem(_ item: NSMenuItem) -> Bool {
|
||||
switch item.action {
|
||||
case #selector(findHide):
|
||||
return focusedSurface?.searchState != nil
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,55 +508,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
window.syncAppearance(surfaceConfig)
|
||||
}
|
||||
|
||||
/// Returns the default size of the window. This is contextual based on the focused surface because
|
||||
/// the focused surface may specify a different default size than others.
|
||||
private var defaultSize: NSRect? {
|
||||
guard let screen = window?.screen ?? NSScreen.main else { return nil }
|
||||
|
||||
if derivedConfig.maximize {
|
||||
return screen.visibleFrame
|
||||
} else if let focusedSurface,
|
||||
let initialSize = focusedSurface.initialSize {
|
||||
// Get the current frame of the window
|
||||
guard var frame = window?.frame else { return nil }
|
||||
|
||||
// Calculate the chrome size (window size minus view size)
|
||||
let chromeWidth = frame.size.width - focusedSurface.frame.size.width
|
||||
let chromeHeight = frame.size.height - focusedSurface.frame.size.height
|
||||
|
||||
// Calculate the new width and height, clamping to the screen's size
|
||||
let newWidth = min(initialSize.width + chromeWidth, screen.visibleFrame.width)
|
||||
let newHeight = min(initialSize.height + chromeHeight, screen.visibleFrame.height)
|
||||
|
||||
// Update the frame size while keeping the window's position intact
|
||||
frame.size.width = newWidth
|
||||
frame.size.height = newHeight
|
||||
|
||||
// Ensure the window doesn't go outside the screen boundaries
|
||||
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
|
||||
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
|
||||
|
||||
return adjustForWindowPosition(frame: frame, on: screen)
|
||||
}
|
||||
|
||||
guard let initialFrame else { return nil }
|
||||
guard var frame = window?.frame else { return nil }
|
||||
|
||||
// Calculate the new width and height, clamping to the screen's size
|
||||
let newWidth = min(initialFrame.size.width, screen.visibleFrame.width)
|
||||
let newHeight = min(initialFrame.size.height, screen.visibleFrame.height)
|
||||
|
||||
// Update the frame size while keeping the window's position intact
|
||||
frame.size.width = newWidth
|
||||
frame.size.height = newHeight
|
||||
|
||||
// Ensure the window doesn't go outside the screen boundaries
|
||||
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
|
||||
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
|
||||
|
||||
return adjustForWindowPosition(frame: frame, on: screen)
|
||||
}
|
||||
|
||||
/// Adjusts the given frame for the configured window position.
|
||||
func adjustForWindowPosition(frame: NSRect, on screen: NSScreen) -> NSRect {
|
||||
guard let x = derivedConfig.windowPositionX else { return frame }
|
||||
@@ -922,9 +873,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
super.windowDidLoad()
|
||||
guard let window else { return }
|
||||
|
||||
// Store our initial frame so we can know our default later.
|
||||
initialFrame = window.frame
|
||||
|
||||
// I copy this because we may change the source in the future but also because
|
||||
// I regularly audit our codebase for "ghostty.config" access because generally
|
||||
// you shouldn't use it. Its safe in this case because for a new window we should
|
||||
@@ -944,19 +892,38 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
// If this is our first surface then our focused surface will be nil
|
||||
// so we force the focused surface to the leaf.
|
||||
focusedSurface = view
|
||||
|
||||
if let defaultSize {
|
||||
window.setFrame(defaultSize, display: true)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize our content view to the SwiftUI root
|
||||
window.contentView = NSHostingView(rootView: TerminalView(
|
||||
ghostty: self.ghostty,
|
||||
viewModel: self,
|
||||
delegate: self
|
||||
delegate: self,
|
||||
))
|
||||
|
||||
|
||||
// If we have a default size, we want to apply it.
|
||||
if let defaultSize {
|
||||
switch (defaultSize) {
|
||||
case .frame:
|
||||
// Frames can be applied immediately
|
||||
defaultSize.apply(to: window)
|
||||
|
||||
case .contentIntrinsicSize:
|
||||
// Content intrinsic size requires a short delay so that AppKit
|
||||
// can layout our SwiftUI views.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(10_000)) { [weak window] in
|
||||
guard let window else { return }
|
||||
defaultSize.apply(to: window)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store our initial frame so we can know our default later. This MUST
|
||||
// be after the defaultSize call above so that we don't re-apply our frame.
|
||||
// Note: we probably want to set this on the first frame change or something
|
||||
// so it respects cascade.
|
||||
initialFrame = window.frame
|
||||
|
||||
// In various situations, macOS automatically tabs new windows. Ghostty handles
|
||||
// its own tabbing so we DONT want this behavior. This detects this scenario and undoes
|
||||
// it.
|
||||
@@ -1144,8 +1111,8 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
}
|
||||
|
||||
@IBAction func returnToDefaultSize(_ sender: Any?) {
|
||||
guard let defaultSize else { return }
|
||||
window?.setFrame(defaultSize, display: true)
|
||||
guard let window, let defaultSize else { return }
|
||||
defaultSize.apply(to: window)
|
||||
}
|
||||
|
||||
@IBAction override func closeWindow(_ sender: Any?) {
|
||||
@@ -1403,8 +1370,8 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
|
||||
// MARK: NSMenuItemValidation
|
||||
|
||||
extension TerminalController: NSMenuItemValidation {
|
||||
func validateMenuItem(_ item: NSMenuItem) -> Bool {
|
||||
extension TerminalController {
|
||||
override func validateMenuItem(_ item: NSMenuItem) -> Bool {
|
||||
switch item.action {
|
||||
case #selector(returnToDefaultSize):
|
||||
guard let window else { return false }
|
||||
@@ -1421,19 +1388,68 @@ extension TerminalController: NSMenuItemValidation {
|
||||
|
||||
// If our window is already the default size or we don't have a
|
||||
// default size, then disable.
|
||||
guard let defaultSize,
|
||||
window.frame.size != .init(
|
||||
width: defaultSize.size.width,
|
||||
height: defaultSize.size.height
|
||||
)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return defaultSize?.isChanged(for: window) ?? false
|
||||
|
||||
default:
|
||||
return true
|
||||
return super.validateMenuItem(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Default Size
|
||||
|
||||
extension TerminalController {
|
||||
/// The possible default sizes for a terminal. The size can't purely be known as a
|
||||
/// window frame because if we set `window-width/height` then it is based
|
||||
/// on content size.
|
||||
enum DefaultSize {
|
||||
/// A frame, set with `window.setFrame`
|
||||
case frame(NSRect)
|
||||
|
||||
/// A content size, set with `window.setContentSize`
|
||||
case contentIntrinsicSize
|
||||
|
||||
func isChanged(for window: NSWindow) -> Bool {
|
||||
switch self {
|
||||
case .frame(let rect):
|
||||
return window.frame != rect
|
||||
case .contentIntrinsicSize:
|
||||
guard let view = window.contentView else {
|
||||
return false
|
||||
}
|
||||
|
||||
return view.frame.size != view.intrinsicContentSize
|
||||
}
|
||||
}
|
||||
|
||||
func apply(to window: NSWindow) {
|
||||
switch self {
|
||||
case .frame(let rect):
|
||||
window.setFrame(rect, display: true)
|
||||
case .contentIntrinsicSize:
|
||||
guard let size = window.contentView?.intrinsicContentSize else {
|
||||
return
|
||||
}
|
||||
|
||||
window.setContentSize(size)
|
||||
window.constrainToScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var defaultSize: DefaultSize? {
|
||||
if derivedConfig.maximize, let screen = window?.screen ?? NSScreen.main {
|
||||
// Maximize takes priority, we take up the full screen we're on.
|
||||
return .frame(screen.visibleFrame)
|
||||
} else if focusedSurface?.initialSize != nil {
|
||||
// Initial size as requested by the configuration (e.g. `window-width`)
|
||||
// takes next priority.
|
||||
return .contentIntrinsicSize
|
||||
} else if let initialFrame {
|
||||
// The initial frame we had when we started otherwise.
|
||||
return .frame(initialFrame)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||
|
||||
// An optional delegate to receive information about terminal changes.
|
||||
weak var delegate: (any TerminalViewDelegate)? = nil
|
||||
|
||||
|
||||
// The most recently focused surface, equal to focusedSurface when
|
||||
// it is non-nil.
|
||||
@State private var lastFocusedSurface: Weak<Ghostty.SurfaceView> = .init()
|
||||
@@ -100,6 +100,8 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||
guard let size = newValue else { return }
|
||||
self.delegate?.cellSizeDidChange(to: size)
|
||||
}
|
||||
.frame(idealWidth: lastFocusedSurface.value?.initialSize?.width,
|
||||
idealHeight: lastFocusedSurface.value?.initialSize?.height)
|
||||
}
|
||||
// Ignore safe area to extend up in to the titlebar region if we have the "hidden" titlebar style
|
||||
.ignoresSafeArea(.container, edges: ghostty.config.macosTitlebarStyle == "hidden" ? .top : [])
|
||||
|
||||
@@ -115,6 +115,18 @@ extension Ghostty.Action {
|
||||
len = c.len
|
||||
}
|
||||
}
|
||||
|
||||
struct StartSearch {
|
||||
let needle: String?
|
||||
|
||||
init(c: ghostty_action_start_search_s) {
|
||||
if let needleCString = c.needle {
|
||||
self.needle = String(cString: needleCString)
|
||||
} else {
|
||||
self.needle = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Putting the initializer in an extension preserves the automatic one.
|
||||
|
||||
@@ -180,14 +180,14 @@ extension Ghostty {
|
||||
|
||||
func newTab(surface: ghostty_surface_t) {
|
||||
let action = "new_tab"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
func newWindow(surface: ghostty_surface_t) {
|
||||
let action = "new_window"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -210,14 +210,14 @@ extension Ghostty {
|
||||
|
||||
func splitToggleZoom(surface: ghostty_surface_t) {
|
||||
let action = "toggle_split_zoom"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
func toggleFullscreen(surface: ghostty_surface_t) {
|
||||
let action = "toggle_fullscreen"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -238,21 +238,21 @@ extension Ghostty {
|
||||
case .reset:
|
||||
action = "reset_font_size"
|
||||
}
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
func toggleTerminalInspector(surface: ghostty_surface_t) {
|
||||
let action = "inspector:toggle"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
func resetTerminal(surface: ghostty_surface_t) {
|
||||
let action = "reset"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -606,6 +606,18 @@ extension Ghostty {
|
||||
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
|
||||
closeAllWindows(app, target: target)
|
||||
|
||||
case GHOSTTY_ACTION_START_SEARCH:
|
||||
startSearch(app, target: target, v: action.action.start_search)
|
||||
|
||||
case GHOSTTY_ACTION_END_SEARCH:
|
||||
endSearch(app, target: target)
|
||||
|
||||
case GHOSTTY_ACTION_SEARCH_TOTAL:
|
||||
searchTotal(app, target: target, v: action.action.search_total)
|
||||
|
||||
case GHOSTTY_ACTION_SEARCH_SELECTED:
|
||||
searchSelected(app, target: target, v: action.action.search_selected)
|
||||
|
||||
case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW:
|
||||
fallthrough
|
||||
case GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS:
|
||||
@@ -1641,6 +1653,100 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
private static func startSearch(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
v: ghostty_action_start_search_s) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("start_search does nothing with an app target")
|
||||
return
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
|
||||
let startSearch = Ghostty.Action.StartSearch(c: v)
|
||||
DispatchQueue.main.async {
|
||||
if surfaceView.searchState != nil {
|
||||
NotificationCenter.default.post(name: .ghosttySearchFocus, object: surfaceView)
|
||||
} else {
|
||||
surfaceView.searchState = Ghostty.SurfaceView.SearchState(from: startSearch)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func endSearch(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("end_search does nothing with an app target")
|
||||
return
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
surfaceView.searchState = nil
|
||||
}
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func searchTotal(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
v: ghostty_action_search_total_s) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("search_total does nothing with an app target")
|
||||
return
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
|
||||
let total: UInt? = v.total >= 0 ? UInt(v.total) : nil
|
||||
DispatchQueue.main.async {
|
||||
surfaceView.searchState?.total = total
|
||||
}
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func searchSelected(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
v: ghostty_action_search_selected_s) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("search_selected does nothing with an app target")
|
||||
return
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
|
||||
let selected: UInt? = v.selected >= 0 ? UInt(v.selected) : nil
|
||||
DispatchQueue.main.async {
|
||||
surfaceView.searchState?.selected = selected
|
||||
}
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func configReload(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
|
||||
@@ -105,7 +105,7 @@ extension Ghostty {
|
||||
func keyboardShortcut(for action: String) -> KeyboardShortcut? {
|
||||
guard let cfg = self.config else { return nil }
|
||||
|
||||
let trigger = ghostty_config_trigger(cfg, action, UInt(action.count))
|
||||
let trigger = ghostty_config_trigger(cfg, action, UInt(action.lengthOfBytes(using: .utf8)))
|
||||
return Ghostty.keyboardShortcut(for: trigger)
|
||||
}
|
||||
#endif
|
||||
@@ -120,7 +120,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return .init() }
|
||||
var v: CUnsignedInt = 0
|
||||
let key = "bell-features"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .init() }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return .init() }
|
||||
return .init(rawValue: v)
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return true }
|
||||
var v = true;
|
||||
let key = "initial-window"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false;
|
||||
let key = "quit-after-last-window-closed"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "title"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return nil }
|
||||
guard let ptr = v else { return nil }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
@@ -153,7 +153,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return "" }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-save-state"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return "" }
|
||||
guard let ptr = v else { return "" }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
@@ -162,21 +162,21 @@ extension Ghostty {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: Int16 = 0
|
||||
let key = "window-position-x"
|
||||
return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil
|
||||
return ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) ? v : nil
|
||||
}
|
||||
|
||||
var windowPositionY: Int16? {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: Int16 = 0
|
||||
let key = "window-position-y"
|
||||
return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil
|
||||
return ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) ? v : nil
|
||||
}
|
||||
|
||||
var windowNewTabPosition: String {
|
||||
guard let config = self.config else { return "" }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-new-tab-position"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return "" }
|
||||
guard let ptr = v else { return "" }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
@@ -186,7 +186,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-decoration"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return WindowDecoration(rawValue: str)?.enabled() ?? defaultValue
|
||||
@@ -196,7 +196,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-theme"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return nil }
|
||||
guard let ptr = v else { return nil }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
@@ -205,7 +205,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false
|
||||
let key = "window-step-resize"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false
|
||||
let key = "fullscreen"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-non-native-fullscreen"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return switch str {
|
||||
@@ -245,7 +245,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "window-title-font-family"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return nil }
|
||||
guard let ptr = v else { return nil }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
@@ -255,7 +255,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-window-buttons"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return MacOSWindowButtons(rawValue: str) ?? defaultValue
|
||||
@@ -266,7 +266,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-titlebar-style"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
return String(cString: ptr)
|
||||
}
|
||||
@@ -276,7 +276,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-titlebar-proxy-icon"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return MacOSTitlebarProxyIcon(rawValue: str) ?? defaultValue
|
||||
@@ -287,7 +287,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-dock-drop-behavior"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return MacDockDropBehavior(rawValue: str) ?? defaultValue
|
||||
@@ -297,7 +297,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return false }
|
||||
var v = false;
|
||||
let key = "macos-window-shadow"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-icon"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return MacOSIcon(rawValue: str) ?? defaultValue
|
||||
@@ -318,7 +318,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-custom-icon"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
guard let path = NSString(utf8String: ptr) else { return defaultValue }
|
||||
return path.expandingTildeInPath
|
||||
@@ -332,7 +332,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-icon-frame"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return MacOSIconFrame(rawValue: str) ?? defaultValue
|
||||
@@ -342,7 +342,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: ghostty_config_color_s = .init()
|
||||
let key = "macos-icon-ghost-color"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return nil }
|
||||
return .init(ghostty: v)
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: ghostty_config_color_list_s = .init()
|
||||
let key = "macos-icon-screen-color"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return nil }
|
||||
guard v.len > 0 else { return nil }
|
||||
let buffer = UnsafeBufferPointer(start: v.colors, count: v.len)
|
||||
return buffer.map { .init(ghostty: $0) }
|
||||
@@ -360,7 +360,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return .never }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-hidden"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .never }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return .never }
|
||||
guard let ptr = v else { return .never }
|
||||
let str = String(cString: ptr)
|
||||
return MacHidden(rawValue: str) ?? .never
|
||||
@@ -370,14 +370,14 @@ extension Ghostty {
|
||||
guard let config = self.config else { return false }
|
||||
var v = false;
|
||||
let key = "focus-follows-mouse"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
var backgroundColor: Color {
|
||||
var color: ghostty_config_color_s = .init();
|
||||
let bg_key = "background"
|
||||
if (!ghostty_config_get(config, &color, bg_key, UInt(bg_key.count))) {
|
||||
if (!ghostty_config_get(config, &color, bg_key, UInt(bg_key.lengthOfBytes(using: .utf8)))) {
|
||||
#if os(macOS)
|
||||
return Color(NSColor.windowBackgroundColor)
|
||||
#elseif os(iOS)
|
||||
@@ -398,7 +398,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return 1 }
|
||||
var v: Double = 1
|
||||
let key = "background-opacity"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return 1 }
|
||||
var v: Int = 0
|
||||
let key = "background-blur"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return 1 }
|
||||
var opacity: Double = 0.85
|
||||
let key = "unfocused-split-opacity"
|
||||
_ = ghostty_config_get(config, &opacity, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &opacity, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return 1 - opacity
|
||||
}
|
||||
|
||||
@@ -423,9 +423,9 @@ extension Ghostty {
|
||||
|
||||
var color: ghostty_config_color_s = .init();
|
||||
let key = "unfocused-split-fill"
|
||||
if (!ghostty_config_get(config, &color, key, UInt(key.count))) {
|
||||
if (!ghostty_config_get(config, &color, key, UInt(key.lengthOfBytes(using: .utf8)))) {
|
||||
let bg_key = "background"
|
||||
_ = ghostty_config_get(config, &color, bg_key, UInt(bg_key.count));
|
||||
_ = ghostty_config_get(config, &color, bg_key, UInt(bg_key.lengthOfBytes(using: .utf8)));
|
||||
}
|
||||
|
||||
return .init(
|
||||
@@ -444,7 +444,7 @@ extension Ghostty {
|
||||
|
||||
var color: ghostty_config_color_s = .init();
|
||||
let key = "split-divider-color"
|
||||
if (!ghostty_config_get(config, &color, key, UInt(key.count))) {
|
||||
if (!ghostty_config_get(config, &color, key, UInt(key.lengthOfBytes(using: .utf8)))) {
|
||||
return Color(newColor)
|
||||
}
|
||||
|
||||
@@ -460,7 +460,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return .top }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "quick-terminal-position"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .top }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return .top }
|
||||
guard let ptr = v else { return .top }
|
||||
let str = String(cString: ptr)
|
||||
return QuickTerminalPosition(rawValue: str) ?? .top
|
||||
@@ -470,7 +470,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return .main }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "quick-terminal-screen"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .main }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return .main }
|
||||
guard let ptr = v else { return .main }
|
||||
let str = String(cString: ptr)
|
||||
return QuickTerminalScreen(fromGhosttyConfig: str) ?? .main
|
||||
@@ -480,7 +480,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return 0.2 }
|
||||
var v: Double = 0.2
|
||||
let key = "quick-terminal-animation-duration"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -488,7 +488,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return true }
|
||||
var v = true
|
||||
let key = "quick-terminal-autohide"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -496,7 +496,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return .move }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "quick-terminal-space-behavior"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .move }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return .move }
|
||||
guard let ptr = v else { return .move }
|
||||
let str = String(cString: ptr)
|
||||
return QuickTerminalSpaceBehavior(fromGhosttyConfig: str) ?? .move
|
||||
@@ -506,7 +506,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return QuickTerminalSize() }
|
||||
var v = ghostty_config_quick_terminal_size_s()
|
||||
let key = "quick-terminal-size"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return QuickTerminalSize() }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return QuickTerminalSize() }
|
||||
return QuickTerminalSize(from: v)
|
||||
}
|
||||
#endif
|
||||
@@ -515,7 +515,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return .after_first }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "resize-overlay"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .after_first }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return .after_first }
|
||||
guard let ptr = v else { return .after_first }
|
||||
let str = String(cString: ptr)
|
||||
return ResizeOverlay(rawValue: str) ?? .after_first
|
||||
@@ -526,7 +526,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "resize-overlay-position"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return ResizeOverlayPosition(rawValue: str) ?? defaultValue
|
||||
@@ -536,7 +536,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return 1000 }
|
||||
var v: UInt = 0
|
||||
let key = "resize-overlay-duration"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -544,7 +544,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return .seconds(5) }
|
||||
var v: UInt = 0
|
||||
let key = "undo-timeout"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return .milliseconds(v)
|
||||
}
|
||||
|
||||
@@ -552,7 +552,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "auto-update"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return nil }
|
||||
guard let ptr = v else { return nil }
|
||||
let str = String(cString: ptr)
|
||||
return AutoUpdate(rawValue: str)
|
||||
@@ -563,7 +563,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "auto-update-channel"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return AutoUpdateChannel(rawValue: str) ?? defaultValue
|
||||
@@ -573,7 +573,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false;
|
||||
let key = "macos-auto-secure-input"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -581,7 +581,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false;
|
||||
let key = "macos-secure-input-indication"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -589,7 +589,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return true }
|
||||
var v = false;
|
||||
let key = "maximize"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -598,7 +598,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-shortcuts"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return MacShortcuts(rawValue: str) ?? defaultValue
|
||||
@@ -609,7 +609,7 @@ extension Ghostty {
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "scrollbar"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return Scrollbar(rawValue: str) ?? defaultValue
|
||||
|
||||
@@ -396,6 +396,9 @@ extension Notification.Name {
|
||||
/// Notification sent when scrollbar updates
|
||||
static let ghosttyDidUpdateScrollbar = Notification.Name("com.mitchellh.ghostty.didUpdateScrollbar")
|
||||
static let ScrollbarKey = ghosttyDidUpdateScrollbar.rawValue + ".scrollbar"
|
||||
|
||||
/// Focus the search field
|
||||
static let ghosttySearchFocus = Notification.Name("com.mitchellh.ghostty.searchFocus")
|
||||
}
|
||||
|
||||
// NOTE: I am moving all of these to Notification.Name extensions over time. This
|
||||
|
||||
@@ -172,13 +172,16 @@ class SurfaceScrollView: NSView {
|
||||
}
|
||||
|
||||
// MARK: Scrolling
|
||||
|
||||
|
||||
private func synchronizeAppearance() {
|
||||
let scrollbarConfig = surfaceView.derivedConfig.scrollbar
|
||||
scrollView.hasVerticalScroller = scrollbarConfig != .never
|
||||
scrollView.verticalScroller?.controlSize = .small
|
||||
let hasLightBackground = OSColor(surfaceView.derivedConfig.backgroundColor).isLightColor
|
||||
// Make sure the scroller’s appearance matches the surface's background color.
|
||||
scrollView.appearance = NSAppearance(named: hasLightBackground ? .aqua : .darkAqua)
|
||||
}
|
||||
|
||||
|
||||
/// Positions the surface view to fill the currently visible rectangle.
|
||||
///
|
||||
/// This is called whenever the scroll position changes. The surface view (which does the
|
||||
|
||||
@@ -197,7 +197,16 @@ extension Ghostty {
|
||||
SecureInputOverlay()
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Search overlay
|
||||
if let searchState = surfaceView.searchState {
|
||||
SurfaceSearchOverlay(
|
||||
surfaceView: surfaceView,
|
||||
searchState: searchState,
|
||||
onClose: { surfaceView.searchState = nil }
|
||||
)
|
||||
}
|
||||
|
||||
// Show bell border if enabled
|
||||
if (ghostty.config.bellFeatures.contains(.border)) {
|
||||
BellBorderOverlay(bell: surfaceView.bell)
|
||||
@@ -382,6 +391,202 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Search overlay view that displays a search bar with input field and navigation buttons.
|
||||
struct SurfaceSearchOverlay: View {
|
||||
let surfaceView: SurfaceView
|
||||
@ObservedObject var searchState: SurfaceView.SearchState
|
||||
let onClose: () -> Void
|
||||
@State private var corner: Corner = .topRight
|
||||
@State private var dragOffset: CGSize = .zero
|
||||
@State private var barSize: CGSize = .zero
|
||||
@FocusState private var isSearchFieldFocused: Bool
|
||||
|
||||
private let padding: CGFloat = 8
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
HStack(spacing: 4) {
|
||||
TextField("Search", text: $searchState.needle)
|
||||
.textFieldStyle(.plain)
|
||||
.frame(width: 180)
|
||||
.padding(.leading, 8)
|
||||
.padding(.trailing, 50)
|
||||
.padding(.vertical, 6)
|
||||
.background(Color.primary.opacity(0.1))
|
||||
.cornerRadius(6)
|
||||
.focused($isSearchFieldFocused)
|
||||
.overlay(alignment: .trailing) {
|
||||
if let selected = searchState.selected {
|
||||
Text("\(selected + 1)/\(searchState.total, default: "?")")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.monospacedDigit()
|
||||
.padding(.trailing, 8)
|
||||
} else if let total = searchState.total {
|
||||
Text("-/\(total)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.monospacedDigit()
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
}
|
||||
#if canImport(AppKit)
|
||||
.onExitCommand {
|
||||
Ghostty.moveFocus(to: surfaceView)
|
||||
}
|
||||
#endif
|
||||
.backport.onKeyPress(.return) { modifiers in
|
||||
guard let surface = surfaceView.surface else { return .ignored }
|
||||
let action = modifiers.contains(.shift)
|
||||
? "navigate_search:previous"
|
||||
: "navigate_search:next"
|
||||
ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))
|
||||
return .handled
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
guard let surface = surfaceView.surface else { return }
|
||||
let action = "navigate_search:next"
|
||||
ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))
|
||||
}) {
|
||||
Image(systemName: "chevron.up")
|
||||
}
|
||||
.buttonStyle(SearchButtonStyle())
|
||||
|
||||
Button(action: {
|
||||
guard let surface = surfaceView.surface else { return }
|
||||
let action = "navigate_search:previous"
|
||||
ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))
|
||||
}) {
|
||||
Image(systemName: "chevron.down")
|
||||
}
|
||||
.buttonStyle(SearchButtonStyle())
|
||||
|
||||
Button(action: onClose) {
|
||||
Image(systemName: "xmark")
|
||||
}
|
||||
.buttonStyle(SearchButtonStyle())
|
||||
}
|
||||
.padding(8)
|
||||
.background(.background)
|
||||
.clipShape(clipShape)
|
||||
.shadow(radius: 4)
|
||||
.onAppear {
|
||||
isSearchFieldFocused = true
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .ghosttySearchFocus)) { notification in
|
||||
guard notification.object as? SurfaceView === surfaceView else { return }
|
||||
isSearchFieldFocused = true
|
||||
}
|
||||
.background(
|
||||
GeometryReader { barGeo in
|
||||
Color.clear.onAppear {
|
||||
barSize = barGeo.size
|
||||
}
|
||||
}
|
||||
)
|
||||
.padding(padding)
|
||||
.offset(dragOffset)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: corner.alignment)
|
||||
.gesture(
|
||||
DragGesture()
|
||||
.onChanged { value in
|
||||
dragOffset = value.translation
|
||||
}
|
||||
.onEnded { value in
|
||||
let centerPos = centerPosition(for: corner, in: geo.size, barSize: barSize)
|
||||
let newCenter = CGPoint(
|
||||
x: centerPos.x + value.translation.width,
|
||||
y: centerPos.y + value.translation.height
|
||||
)
|
||||
let newCorner = closestCorner(to: newCenter, in: geo.size)
|
||||
withAnimation(.easeOut(duration: 0.2)) {
|
||||
corner = newCorner
|
||||
dragOffset = .zero
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var clipShape: some Shape {
|
||||
if #available(iOS 26.0, macOS 26.0, *) {
|
||||
return ConcentricRectangle(corners: .concentric(minimum: 8), isUniform: true)
|
||||
} else {
|
||||
return RoundedRectangle(cornerRadius: 8)
|
||||
}
|
||||
}
|
||||
|
||||
enum Corner {
|
||||
case topLeft, topRight, bottomLeft, bottomRight
|
||||
|
||||
var alignment: Alignment {
|
||||
switch self {
|
||||
case .topLeft: return .topLeading
|
||||
case .topRight: return .topTrailing
|
||||
case .bottomLeft: return .bottomLeading
|
||||
case .bottomRight: return .bottomTrailing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func centerPosition(for corner: Corner, in containerSize: CGSize, barSize: CGSize) -> CGPoint {
|
||||
let halfWidth = barSize.width / 2 + padding
|
||||
let halfHeight = barSize.height / 2 + padding
|
||||
|
||||
switch corner {
|
||||
case .topLeft:
|
||||
return CGPoint(x: halfWidth, y: halfHeight)
|
||||
case .topRight:
|
||||
return CGPoint(x: containerSize.width - halfWidth, y: halfHeight)
|
||||
case .bottomLeft:
|
||||
return CGPoint(x: halfWidth, y: containerSize.height - halfHeight)
|
||||
case .bottomRight:
|
||||
return CGPoint(x: containerSize.width - halfWidth, y: containerSize.height - halfHeight)
|
||||
}
|
||||
}
|
||||
|
||||
private func closestCorner(to point: CGPoint, in containerSize: CGSize) -> Corner {
|
||||
let midX = containerSize.width / 2
|
||||
let midY = containerSize.height / 2
|
||||
|
||||
if point.x < midX {
|
||||
return point.y < midY ? .topLeft : .bottomLeft
|
||||
} else {
|
||||
return point.y < midY ? .topRight : .bottomRight
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchButtonStyle: ButtonStyle {
|
||||
@State private var isHovered = false
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.foregroundStyle(isHovered || configuration.isPressed ? .primary : .secondary)
|
||||
.padding(.horizontal, 2)
|
||||
.frame(height: 26)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(backgroundColor(isPressed: configuration.isPressed))
|
||||
)
|
||||
.onHover { hovering in
|
||||
isHovered = hovering
|
||||
}
|
||||
.backport.pointerStyle(.link)
|
||||
}
|
||||
|
||||
private func backgroundColor(isPressed: Bool) -> Color {
|
||||
if isPressed {
|
||||
return Color.primary.opacity(0.2)
|
||||
} else if isHovered {
|
||||
return Color.primary.opacity(0.1)
|
||||
} else {
|
||||
return Color.clear
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A surface is terminology in Ghostty for a terminal surface, or a place where a terminal is actually drawn
|
||||
/// and interacted with. The word "surface" is used because a surface may represent a window, a tab,
|
||||
/// a split, a small preview pane, etc. It is ANYTHING that has a terminal drawn to it.
|
||||
@@ -658,3 +863,17 @@ extension FocusedValues {
|
||||
typealias Value = OSSize
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Search State
|
||||
|
||||
extension Ghostty.SurfaceView {
|
||||
class SearchState: ObservableObject {
|
||||
@Published var needle: String = ""
|
||||
@Published var selected: UInt? = nil
|
||||
@Published var total: UInt? = nil
|
||||
|
||||
init(from startSearch: Ghostty.Action.StartSearch) {
|
||||
self.needle = startSearch.needle ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import AppKit
|
||||
import Combine
|
||||
import SwiftUI
|
||||
import CoreText
|
||||
import UserNotifications
|
||||
@@ -64,6 +65,43 @@ extension Ghostty {
|
||||
// The currently active key sequence. The sequence is not active if this is empty.
|
||||
@Published var keySequence: [KeyboardShortcut] = []
|
||||
|
||||
// The current search state. When non-nil, the search overlay should be shown.
|
||||
@Published var searchState: SearchState? = nil {
|
||||
didSet {
|
||||
if let searchState {
|
||||
// I'm not a Combine expert so if there is a better way to do this I'm
|
||||
// all ears. What we're doing here is grabbing the latest needle. If the
|
||||
// needle is less than 3 chars, we debounce it for a few hundred ms to
|
||||
// avoid kicking off expensive searches.
|
||||
searchNeedleCancellable = searchState.$needle
|
||||
.removeDuplicates()
|
||||
.map { needle -> AnyPublisher<String, Never> in
|
||||
if needle.isEmpty || needle.count >= 3 {
|
||||
return Just(needle).eraseToAnyPublisher()
|
||||
} else {
|
||||
return Just(needle)
|
||||
.delay(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
.switchToLatest()
|
||||
.sink { [weak self] needle in
|
||||
guard let surface = self?.surface else { return }
|
||||
let action = "search:\(needle)"
|
||||
ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))
|
||||
}
|
||||
} else if oldValue != nil {
|
||||
searchNeedleCancellable = nil
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "end_search"
|
||||
ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cancellable for search state needle changes
|
||||
private var searchNeedleCancellable: AnyCancellable?
|
||||
|
||||
// The time this surface last became focused. This is a ContinuousClock.Instant
|
||||
// on supported platforms.
|
||||
@Published var focusInstant: ContinuousClock.Instant? = nil
|
||||
@@ -1410,7 +1448,7 @@ extension Ghostty {
|
||||
@IBAction func copy(_ sender: Any?) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "copy_to_clipboard"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -1418,7 +1456,7 @@ extension Ghostty {
|
||||
@IBAction func paste(_ sender: Any?) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "paste_from_clipboard"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -1427,7 +1465,7 @@ extension Ghostty {
|
||||
@IBAction func pasteAsPlainText(_ sender: Any?) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "paste_from_clipboard"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -1435,7 +1473,7 @@ extension Ghostty {
|
||||
@IBAction func pasteSelection(_ sender: Any?) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "paste_from_selection"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -1443,7 +1481,39 @@ extension Ghostty {
|
||||
@IBAction override func selectAll(_ sender: Any?) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "select_all"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func find(_ sender: Any?) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "start_search"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func findNext(_ sender: Any?) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "search:next"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func findPrevious(_ sender: Any?) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "search:previous"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func findHide(_ sender: Any?) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "end_search"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -1471,7 +1541,7 @@ extension Ghostty {
|
||||
@objc func resetTerminal(_ sender: Any) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "reset"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -1479,7 +1549,7 @@ extension Ghostty {
|
||||
@objc func toggleTerminalInspector(_ sender: Any) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "inspector:toggle"
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
|
||||
if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
|
||||
AppDelegate.logger.warning("action failed action=\(action)")
|
||||
}
|
||||
}
|
||||
@@ -1740,7 +1810,13 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
||||
} else {
|
||||
ghostty_surface_ime_point(surface, &x, &y, &width, &height)
|
||||
}
|
||||
|
||||
if range.length == 0, width > 0 {
|
||||
// This fixes #8493 while speaking
|
||||
// My guess is that positive width doesn't make sense
|
||||
// for the dictation microphone indicator
|
||||
width = 0
|
||||
x += cellSize.width * Double(range.location + range.length)
|
||||
}
|
||||
// Ghostty coordinates are in top-left (0, 0) so we have to convert to
|
||||
// bottom-left since that is what UIKit expects
|
||||
// when there's is no characters selected,
|
||||
@@ -1914,6 +1990,9 @@ extension Ghostty.SurfaceView: NSMenuItemValidation {
|
||||
let pb = NSPasteboard.ghosttySelection
|
||||
guard let str = pb.getOpinionatedStringContents() else { return false }
|
||||
return !str.isEmpty
|
||||
|
||||
case #selector(findHide):
|
||||
return searchState != nil
|
||||
|
||||
default:
|
||||
return true
|
||||
|
||||
@@ -40,6 +40,9 @@ extension Ghostty {
|
||||
|
||||
/// True when the bell is active. This is set inactive on focus or event.
|
||||
@Published var bell: Bool = false
|
||||
|
||||
// The current search state. When non-nil, the search overlay should be shown.
|
||||
@Published var searchState: SearchState? = nil
|
||||
|
||||
// Returns sizing information for the surface. This is the raw C
|
||||
// structure because I'm lazy.
|
||||
|
||||
@@ -18,6 +18,12 @@ extension Backport where Content: Scene {
|
||||
// None currently
|
||||
}
|
||||
|
||||
/// Result type for backported onKeyPress handler
|
||||
enum BackportKeyPressResult {
|
||||
case handled
|
||||
case ignored
|
||||
}
|
||||
|
||||
extension Backport where Content: View {
|
||||
func pointerVisibility(_ v: BackportVisibility) -> some View {
|
||||
#if canImport(AppKit)
|
||||
@@ -42,6 +48,24 @@ extension Backport where Content: View {
|
||||
return content
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Backported onKeyPress that works on macOS 14+ and is a no-op on macOS 13.
|
||||
func onKeyPress(_ key: KeyEquivalent, action: @escaping (EventModifiers) -> BackportKeyPressResult) -> some View {
|
||||
#if canImport(AppKit)
|
||||
if #available(macOS 14, *) {
|
||||
return content.onKeyPress(key, phases: .down, action: { keyPress in
|
||||
switch action(keyPress.modifiers) {
|
||||
case .handled: return .handled
|
||||
case .ignored: return .ignored
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return content
|
||||
}
|
||||
#else
|
||||
return content
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
enum BackportVisibility {
|
||||
|
||||
@@ -15,4 +15,20 @@ extension NSWindow {
|
||||
guard let firstWindow = tabGroup?.windows.first else { return true }
|
||||
return firstWindow === self
|
||||
}
|
||||
|
||||
/// Adjusts the window origin if necessary to ensure the window remains visible on screen.
|
||||
func constrainToScreen() {
|
||||
guard let screen = screen ?? NSScreen.main else { return }
|
||||
let visibleFrame = screen.visibleFrame
|
||||
var windowFrame = frame
|
||||
|
||||
windowFrame.origin.x = max(visibleFrame.minX,
|
||||
min(windowFrame.origin.x, visibleFrame.maxX - windowFrame.width))
|
||||
windowFrame.origin.y = max(visibleFrame.minY,
|
||||
min(windowFrame.origin.y, visibleFrame.maxY - windowFrame.height))
|
||||
|
||||
if windowFrame.origin != frame.origin {
|
||||
setFrameOrigin(windowFrame.origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,12 @@ pub const LangSet = opaque {
|
||||
c.FcLangSetDestroy(self.cval());
|
||||
}
|
||||
|
||||
pub fn addLang(self: *LangSet, lang: [:0]const u8) bool {
|
||||
return c.FcLangSetAdd(self.cval(), lang.ptr) == c.FcTrue;
|
||||
}
|
||||
|
||||
pub fn hasLang(self: *const LangSet, lang: [:0]const u8) bool {
|
||||
return c.FcLangSetHasLang(self.cvalConst(), lang.ptr) == c.FcTrue;
|
||||
return c.FcLangSetHasLang(self.cvalConst(), lang.ptr) == c.FcLangEqual;
|
||||
}
|
||||
|
||||
pub inline fn cval(self: *LangSet) *c.struct__FcLangSet {
|
||||
@@ -32,3 +36,26 @@ test "create" {
|
||||
|
||||
try testing.expect(!fs.hasLang("und-zsye"));
|
||||
}
|
||||
|
||||
test "hasLang exact match" {
|
||||
const testing = std.testing;
|
||||
|
||||
// Test exact match: langset with "en-US" should return true for "en-US"
|
||||
var fs = LangSet.create();
|
||||
defer fs.destroy();
|
||||
try testing.expect(fs.addLang("en-US"));
|
||||
try testing.expect(fs.hasLang("en-US"));
|
||||
|
||||
// Test exact match: langset with "und-zsye" should return true for "und-zsye"
|
||||
var fs_emoji = LangSet.create();
|
||||
defer fs_emoji.destroy();
|
||||
try testing.expect(fs_emoji.addLang("und-zsye"));
|
||||
try testing.expect(fs_emoji.hasLang("und-zsye"));
|
||||
|
||||
// Test mismatch: langset with "en-US" should return false for "fr"
|
||||
try testing.expect(!fs.hasLang("fr"));
|
||||
|
||||
// Test partial match: langset with "en-US" should return false for "en-GB"
|
||||
// (different territory, but we only want exact matches)
|
||||
try testing.expect(!fs.hasLang("en-GB"));
|
||||
}
|
||||
|
||||
@@ -252,9 +252,13 @@ pub const RenderMode = enum(c_uint) {
|
||||
sdf = c.FT_RENDER_MODE_SDF,
|
||||
};
|
||||
|
||||
/// A list of bit field constants for FT_Load_Glyph to indicate what kind of
|
||||
/// operations to perform during glyph loading.
|
||||
pub const LoadFlags = packed struct {
|
||||
/// A collection of flags for FT_Load_Glyph that indicate
|
||||
/// what kind of operations to perform during glyph loading.
|
||||
///
|
||||
/// Some of these flags are not included in the official FreeType
|
||||
/// documentation, but are nevertheless present and named in the
|
||||
/// header, so the names have been copied from there.
|
||||
pub const LoadFlags = packed struct(c_int) {
|
||||
no_scale: bool = false,
|
||||
no_hinting: bool = false,
|
||||
render: bool = false,
|
||||
@@ -263,39 +267,97 @@ pub const LoadFlags = packed struct {
|
||||
force_autohint: bool = false,
|
||||
crop_bitmap: bool = false,
|
||||
pedantic: bool = false,
|
||||
ignore_global_advance_with: bool = false,
|
||||
advance_only: bool = false,
|
||||
ignore_global_advance_width: bool = false,
|
||||
no_recurse: bool = false,
|
||||
ignore_transform: bool = false,
|
||||
monochrome: bool = false,
|
||||
linear_design: bool = false,
|
||||
sbits_only: bool = false,
|
||||
no_autohint: bool = false,
|
||||
_padding1: u1 = 0,
|
||||
target_normal: bool = false,
|
||||
target_light: bool = false,
|
||||
target_mono: bool = false,
|
||||
target_lcd: bool = false,
|
||||
target_lcd_v: bool = false,
|
||||
target: Target = .normal,
|
||||
color: bool = false,
|
||||
compute_metrics: bool = false,
|
||||
bitmap_metrics_only: bool = false,
|
||||
_padding2: u1 = 0,
|
||||
svg_only: bool = false,
|
||||
no_svg: bool = false,
|
||||
_padding3: u7 = 0,
|
||||
_padding: u7 = 0,
|
||||
|
||||
test {
|
||||
// This must always be an i32 size so we can bitcast directly.
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(@sizeOf(i32), @sizeOf(LoadFlags));
|
||||
}
|
||||
pub const Target = enum(u4) {
|
||||
normal = 0,
|
||||
light = 1,
|
||||
mono = 2,
|
||||
lcd = 3,
|
||||
lcd_v = 4,
|
||||
};
|
||||
|
||||
test "bitcast" {
|
||||
const testing = std.testing;
|
||||
|
||||
const cval: i32 = c.FT_LOAD_RENDER | c.FT_LOAD_PEDANTIC | c.FT_LOAD_COLOR;
|
||||
const flags = @as(LoadFlags, @bitCast(cval));
|
||||
try testing.expect(!flags.no_hinting);
|
||||
try testing.expect(flags.render);
|
||||
try testing.expect(flags.pedantic);
|
||||
try testing.expect(flags.color);
|
||||
|
||||
// Verify bit alignment (for bit 9)
|
||||
const cval2: i32 = c.FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH;
|
||||
const flags2 = @as(LoadFlags, @bitCast(cval2));
|
||||
try testing.expect(flags2.ignore_global_advance_width);
|
||||
try testing.expect(!flags2.no_recurse);
|
||||
}
|
||||
|
||||
test "all flags individually" {
|
||||
const testing = std.testing;
|
||||
|
||||
try testing.expectEqual(
|
||||
c.FT_LOAD_DEFAULT,
|
||||
@as(c_int, @bitCast(LoadFlags{})),
|
||||
);
|
||||
|
||||
inline for ([_]struct { c_int, []const u8 }{
|
||||
.{ c.FT_LOAD_NO_SCALE, "no_scale" },
|
||||
.{ c.FT_LOAD_NO_HINTING, "no_hinting" },
|
||||
.{ c.FT_LOAD_RENDER, "render" },
|
||||
.{ c.FT_LOAD_NO_BITMAP, "no_bitmap" },
|
||||
.{ c.FT_LOAD_VERTICAL_LAYOUT, "vertical_layout" },
|
||||
.{ c.FT_LOAD_FORCE_AUTOHINT, "force_autohint" },
|
||||
.{ c.FT_LOAD_CROP_BITMAP, "crop_bitmap" },
|
||||
.{ c.FT_LOAD_PEDANTIC, "pedantic" },
|
||||
.{ c.FT_LOAD_ADVANCE_ONLY, "advance_only" },
|
||||
.{ c.FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, "ignore_global_advance_width" },
|
||||
.{ c.FT_LOAD_NO_RECURSE, "no_recurse" },
|
||||
.{ c.FT_LOAD_IGNORE_TRANSFORM, "ignore_transform" },
|
||||
.{ c.FT_LOAD_MONOCHROME, "monochrome" },
|
||||
.{ c.FT_LOAD_LINEAR_DESIGN, "linear_design" },
|
||||
.{ c.FT_LOAD_SBITS_ONLY, "sbits_only" },
|
||||
.{ c.FT_LOAD_NO_AUTOHINT, "no_autohint" },
|
||||
.{ c.FT_LOAD_COLOR, "color" },
|
||||
.{ c.FT_LOAD_COMPUTE_METRICS, "compute_metrics" },
|
||||
.{ c.FT_LOAD_BITMAP_METRICS_ONLY, "bitmap_metrics_only" },
|
||||
.{ c.FT_LOAD_SVG_ONLY, "svg_only" },
|
||||
.{ c.FT_LOAD_NO_SVG, "no_svg" },
|
||||
}) |pair| {
|
||||
var flags: LoadFlags = .{};
|
||||
@field(flags, pair[1]) = true;
|
||||
try testing.expectEqual(pair[0], @as(c_int, @bitCast(flags)));
|
||||
}
|
||||
}
|
||||
|
||||
test "all load targets" {
|
||||
const testing = std.testing;
|
||||
|
||||
inline for ([_]struct { c_int, Target }{
|
||||
.{ c.FT_LOAD_TARGET_NORMAL, .normal },
|
||||
.{ c.FT_LOAD_TARGET_LIGHT, .light },
|
||||
.{ c.FT_LOAD_TARGET_MONO, .mono },
|
||||
.{ c.FT_LOAD_TARGET_LCD, .lcd },
|
||||
.{ c.FT_LOAD_TARGET_LCD_V, .lcd_v },
|
||||
}) |pair| {
|
||||
const flags: LoadFlags = .{ .target = pair[1] };
|
||||
try testing.expectEqual(pair[0], @as(c_int, @bitCast(flags)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
BIN
pkg/freetype/res/FiraCode-Regular.ttf
Executable file
BIN
pkg/freetype/res/FiraCode-Regular.ttf
Executable file
Binary file not shown.
@@ -1 +1 @@
|
||||
pub const font_regular = @embedFile("res/JetBrainsMono-Regular.ttf");
|
||||
pub const font_regular = @embedFile("res/FiraCode-Regular.ttf");
|
||||
|
||||
@@ -67,6 +67,10 @@ pub fn build(b: *std.Build) !void {
|
||||
"-fno-cxx-exceptions",
|
||||
"-fno-slp-vectorize",
|
||||
"-fno-vectorize",
|
||||
|
||||
// Fixes linker issues for release builds missing ubsanitizer symbols
|
||||
"-fno-sanitize=undefined",
|
||||
"-fno-sanitize-trap=undefined",
|
||||
});
|
||||
if (target.result.os.tag != .windows) {
|
||||
try flags.appendSlice(b.allocator, &.{
|
||||
|
||||
@@ -24,7 +24,13 @@ pub fn build(b: *std.Build) !void {
|
||||
defer flags.deinit(b.allocator);
|
||||
// Zig 0.13 bug: https://github.com/ziglang/zig/issues/20414
|
||||
// (See root Ghostty build.zig on why we do this)
|
||||
try flags.appendSlice(b.allocator, &.{"-DSIMDUTF_IMPLEMENTATION_ICELAKE=0"});
|
||||
try flags.appendSlice(b.allocator, &.{
|
||||
"-DSIMDUTF_IMPLEMENTATION_ICELAKE=0",
|
||||
|
||||
// Fixes linker issues for release builds missing ubsanitizer symbols
|
||||
"-fno-sanitize=undefined",
|
||||
"-fno-sanitize-trap=undefined",
|
||||
});
|
||||
|
||||
lib.addCSourceFiles(.{
|
||||
.flags = flags.items,
|
||||
|
||||
@@ -5,21 +5,16 @@ const App = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const build_config = @import("build_config.zig");
|
||||
const apprt = @import("apprt.zig");
|
||||
const Surface = @import("Surface.zig");
|
||||
const tracy = @import("tracy");
|
||||
const input = @import("input.zig");
|
||||
const configpkg = @import("config.zig");
|
||||
const Config = configpkg.Config;
|
||||
const BlockingQueue = @import("datastruct/main.zig").BlockingQueue;
|
||||
const renderer = @import("renderer.zig");
|
||||
const font = @import("font/main.zig");
|
||||
const internal_os = @import("os/main.zig");
|
||||
const macos = @import("macos");
|
||||
const objc = @import("objc");
|
||||
|
||||
const log = std.log.scoped(.app);
|
||||
|
||||
|
||||
261
src/Surface.zig
261
src/Surface.zig
@@ -17,7 +17,7 @@ pub const Message = apprt.surface.Message;
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const global_state = &@import("global.zig").state;
|
||||
@@ -26,9 +26,6 @@ const crash = @import("crash/main.zig");
|
||||
const unicode = @import("unicode/main.zig");
|
||||
const rendererpkg = @import("renderer.zig");
|
||||
const termio = @import("termio.zig");
|
||||
const objc = @import("objc");
|
||||
const imgui = @import("imgui");
|
||||
const Pty = @import("pty.zig").Pty;
|
||||
const font = @import("font/main.zig");
|
||||
const Command = @import("Command.zig");
|
||||
const terminal = @import("terminal/main.zig");
|
||||
@@ -155,6 +152,9 @@ selection_scroll_active: bool = false,
|
||||
/// the wall clock time that has elapsed between timestamps.
|
||||
command_timer: ?std.time.Instant = null,
|
||||
|
||||
/// Search state
|
||||
search: ?Search = null,
|
||||
|
||||
/// The effect of an input event. This can be used by callers to take
|
||||
/// the appropriate action after an input event. For example, key
|
||||
/// input can be forwarded to the OS for further processing if it
|
||||
@@ -174,6 +174,26 @@ pub const InputEffect = enum {
|
||||
closed,
|
||||
};
|
||||
|
||||
/// The search state for the surface.
|
||||
const Search = struct {
|
||||
state: terminal.search.Thread,
|
||||
thread: std.Thread,
|
||||
|
||||
pub fn deinit(self: *Search) void {
|
||||
// Notify the thread to stop
|
||||
self.state.stop.notify() catch |err| log.err(
|
||||
"error notifying search thread to stop, may stall err={}",
|
||||
.{err},
|
||||
);
|
||||
|
||||
// Wait for the OS thread to quit
|
||||
self.thread.join();
|
||||
|
||||
// Now it is safe to deinit the state
|
||||
self.state.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
/// Mouse state for the surface.
|
||||
const Mouse = struct {
|
||||
/// The last tracked mouse button state by button.
|
||||
@@ -728,6 +748,9 @@ pub fn init(
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Surface) void {
|
||||
// Stop search thread
|
||||
if (self.search) |*s| s.deinit();
|
||||
|
||||
// Stop rendering thread
|
||||
{
|
||||
self.renderer_thread.stop.notify() catch |err|
|
||||
@@ -778,6 +801,14 @@ pub fn close(self: *Surface) void {
|
||||
self.rt_surface.close(self.needsConfirmQuit());
|
||||
}
|
||||
|
||||
/// Returns a mailbox that can be used to send messages to this surface.
|
||||
inline fn surfaceMailbox(self: *Surface) Mailbox {
|
||||
return .{
|
||||
.surface = self,
|
||||
.app = .{ .rt_app = self.rt_app, .mailbox = &self.app.mailbox },
|
||||
};
|
||||
}
|
||||
|
||||
/// Forces the surface to render. This is useful for when the surface
|
||||
/// is in the middle of animation (such as a resize, etc.) or when
|
||||
/// the render timer is managed manually by the apprt.
|
||||
@@ -1043,6 +1074,22 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
||||
log.warn("apprt failed to notify command finish={}", .{err});
|
||||
};
|
||||
},
|
||||
|
||||
.search_total => |v| {
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.search_total,
|
||||
.{ .total = v },
|
||||
);
|
||||
},
|
||||
|
||||
.search_selected => |v| {
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.search_selected,
|
||||
.{ .selected = v },
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1301,6 +1348,118 @@ fn reportColorScheme(self: *Surface, force: bool) void {
|
||||
self.io.queueMessage(.{ .write_stable = output }, .unlocked);
|
||||
}
|
||||
|
||||
fn searchCallback(event: terminal.search.Thread.Event, ud: ?*anyopaque) void {
|
||||
// IMPORTANT: This function is run on the SEARCH THREAD! It is NOT SAFE
|
||||
// to access anything other than values that never change on the surface.
|
||||
// The surface is guaranteed to be valid for the lifetime of the search
|
||||
// thread.
|
||||
const self: *Surface = @ptrCast(@alignCast(ud.?));
|
||||
self.searchCallback_(event) catch |err| {
|
||||
log.warn("error in search callback err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
fn searchCallback_(
|
||||
self: *Surface,
|
||||
event: terminal.search.Thread.Event,
|
||||
) !void {
|
||||
// NOTE: This runs on the search thread.
|
||||
|
||||
switch (event) {
|
||||
.viewport_matches => |matches_unowned| {
|
||||
var arena: ArenaAllocator = .init(self.alloc);
|
||||
errdefer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const matches = try alloc.dupe(terminal.highlight.Flattened, matches_unowned);
|
||||
for (matches) |*m| m.* = try m.clone(alloc);
|
||||
|
||||
_ = self.renderer_thread.mailbox.push(
|
||||
.{ .search_viewport_matches = .{
|
||||
.arena = arena,
|
||||
.matches = matches,
|
||||
} },
|
||||
.forever,
|
||||
);
|
||||
try self.renderer_thread.wakeup.notify();
|
||||
},
|
||||
|
||||
.selected_match => |selected_| {
|
||||
if (selected_) |sel| {
|
||||
// Copy the flattened match.
|
||||
var arena: ArenaAllocator = .init(self.alloc);
|
||||
errdefer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
const match = try sel.highlight.clone(alloc);
|
||||
|
||||
_ = self.renderer_thread.mailbox.push(
|
||||
.{ .search_selected_match = .{
|
||||
.arena = arena,
|
||||
.match = match,
|
||||
} },
|
||||
.forever,
|
||||
);
|
||||
|
||||
// Send the selected index to the surface mailbox
|
||||
_ = self.surfaceMailbox().push(
|
||||
.{ .search_selected = sel.idx },
|
||||
.forever,
|
||||
);
|
||||
} else {
|
||||
// Reset our selected match
|
||||
_ = self.renderer_thread.mailbox.push(
|
||||
.{ .search_selected_match = null },
|
||||
.forever,
|
||||
);
|
||||
|
||||
// Reset the selected index
|
||||
_ = self.surfaceMailbox().push(
|
||||
.{ .search_selected = null },
|
||||
.forever,
|
||||
);
|
||||
}
|
||||
|
||||
try self.renderer_thread.wakeup.notify();
|
||||
},
|
||||
|
||||
.total_matches => |total| {
|
||||
_ = self.surfaceMailbox().push(
|
||||
.{ .search_total = total },
|
||||
.forever,
|
||||
);
|
||||
},
|
||||
|
||||
// When we quit, tell our renderer to reset any search state.
|
||||
.quit => {
|
||||
_ = self.renderer_thread.mailbox.push(
|
||||
.{ .search_selected_match = null },
|
||||
.forever,
|
||||
);
|
||||
_ = self.renderer_thread.mailbox.push(
|
||||
.{ .search_viewport_matches = .{
|
||||
.arena = .init(self.alloc),
|
||||
.matches = &.{},
|
||||
} },
|
||||
.forever,
|
||||
);
|
||||
try self.renderer_thread.wakeup.notify();
|
||||
|
||||
// Reset search totals in the surface
|
||||
_ = self.surfaceMailbox().push(
|
||||
.{ .search_total = null },
|
||||
.forever,
|
||||
);
|
||||
_ = self.surfaceMailbox().push(
|
||||
.{ .search_selected = null },
|
||||
.forever,
|
||||
);
|
||||
},
|
||||
|
||||
// Unhandled, so far.
|
||||
.complete => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Call this when modifiers change. This is safe to call even if modifiers
|
||||
/// match the previous state.
|
||||
///
|
||||
@@ -3305,6 +3464,8 @@ fn mouseReport(
|
||||
.five => 65,
|
||||
.six => 66,
|
||||
.seven => 67,
|
||||
.eight => 128,
|
||||
.nine => 129,
|
||||
else => return, // unsupported
|
||||
};
|
||||
}
|
||||
@@ -4103,7 +4264,7 @@ fn osc8URI(self: *Surface, pin: terminal.Pin) ?[]const u8 {
|
||||
const cell = pin.rowAndCell().cell;
|
||||
const link_id = page.lookupHyperlink(cell) orelse return null;
|
||||
const entry = page.hyperlink_set.get(page.memory, link_id);
|
||||
return entry.uri.offset.ptr(page.memory)[0..entry.uri.len];
|
||||
return entry.uri.slice(page.memory);
|
||||
}
|
||||
|
||||
pub fn mousePressureCallback(
|
||||
@@ -4770,6 +4931,96 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
self.renderer_state.terminal.fullReset();
|
||||
},
|
||||
|
||||
.start_search => {
|
||||
// To save resources, we don't actually start a search here,
|
||||
// we just notify the apprt. The real thread will start when
|
||||
// the first needles are set.
|
||||
return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.start_search,
|
||||
.{ .needle = "" },
|
||||
);
|
||||
},
|
||||
|
||||
.end_search => {
|
||||
// We only return that this was performed if we actually
|
||||
// stopped a search, but we also send the apprt end_search so
|
||||
// that GUIs can clean up stale stuff.
|
||||
const performed = self.search != null;
|
||||
|
||||
if (self.search) |*s| {
|
||||
s.deinit();
|
||||
self.search = null;
|
||||
}
|
||||
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.end_search,
|
||||
{},
|
||||
);
|
||||
|
||||
return performed;
|
||||
},
|
||||
|
||||
.search => |text| search: {
|
||||
const s: *Search = if (self.search) |*s| s else init: {
|
||||
// If we're stopping the search and we had no prior search,
|
||||
// then there is nothing to do.
|
||||
if (text.len == 0) return false;
|
||||
|
||||
// We need to assign directly to self.search because we need
|
||||
// a stable pointer back to the thread state.
|
||||
self.search = .{
|
||||
.state = try .init(self.alloc, .{
|
||||
.mutex = self.renderer_state.mutex,
|
||||
.terminal = self.renderer_state.terminal,
|
||||
.event_cb = &searchCallback,
|
||||
.event_userdata = self,
|
||||
}),
|
||||
.thread = undefined,
|
||||
};
|
||||
const s: *Search = &self.search.?;
|
||||
errdefer s.state.deinit();
|
||||
|
||||
s.thread = try .spawn(
|
||||
.{},
|
||||
terminal.search.Thread.threadMain,
|
||||
.{&s.state},
|
||||
);
|
||||
s.thread.setName("search") catch {};
|
||||
|
||||
break :init s;
|
||||
};
|
||||
|
||||
// Zero-length text means stop searching.
|
||||
if (text.len == 0) {
|
||||
s.deinit();
|
||||
self.search = null;
|
||||
break :search;
|
||||
}
|
||||
|
||||
_ = s.state.mailbox.push(
|
||||
.{ .change_needle = try .init(
|
||||
self.alloc,
|
||||
text,
|
||||
) },
|
||||
.forever,
|
||||
);
|
||||
s.state.wakeup.notify() catch {};
|
||||
},
|
||||
|
||||
.navigate_search => |nav| {
|
||||
const s: *Search = if (self.search) |*s| s else return false;
|
||||
_ = s.state.mailbox.push(
|
||||
.{ .select = switch (nav) {
|
||||
.next => .next,
|
||||
.previous => .prev,
|
||||
} },
|
||||
.forever,
|
||||
);
|
||||
s.state.wakeup.notify() catch {};
|
||||
},
|
||||
|
||||
.copy_to_clipboard => |format| {
|
||||
// We can read from the renderer state without holding
|
||||
// the lock because only we will write to this field.
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
//! The goal is to have different implementations share as much of the core
|
||||
//! logic as possible, and to only reach out to platform-specific implementation
|
||||
//! code when absolutely necessary.
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const build_config = @import("build_config.zig");
|
||||
|
||||
const structs = @import("apprt/structs.zig");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
const build_config = @import("../build_config.zig");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const apprt = @import("../apprt.zig");
|
||||
const configpkg = @import("../config.zig");
|
||||
const input = @import("../input.zig");
|
||||
@@ -301,6 +301,18 @@ pub const Action = union(Key) {
|
||||
/// A command has finished,
|
||||
command_finished: CommandFinished,
|
||||
|
||||
/// Start the search overlay with an optional initial needle.
|
||||
start_search: StartSearch,
|
||||
|
||||
/// End the search overlay, clearing the search state and hiding it.
|
||||
end_search,
|
||||
|
||||
/// The total number of matches found by the search.
|
||||
search_total: SearchTotal,
|
||||
|
||||
/// The currently selected search match index (1-based).
|
||||
search_selected: SearchSelected,
|
||||
|
||||
/// Sync with: ghostty_action_tag_e
|
||||
pub const Key = enum(c_int) {
|
||||
quit,
|
||||
@@ -358,6 +370,10 @@ pub const Action = union(Key) {
|
||||
progress_report,
|
||||
show_on_screen_keyboard,
|
||||
command_finished,
|
||||
start_search,
|
||||
end_search,
|
||||
search_total,
|
||||
search_selected,
|
||||
};
|
||||
|
||||
/// Sync with: ghostty_action_u
|
||||
@@ -770,3 +786,48 @@ pub const CommandFinished = struct {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const StartSearch = struct {
|
||||
needle: [:0]const u8,
|
||||
|
||||
// Sync with: ghostty_action_start_search_s
|
||||
pub const C = extern struct {
|
||||
needle: [*:0]const u8,
|
||||
};
|
||||
|
||||
pub fn cval(self: StartSearch) C {
|
||||
return .{
|
||||
.needle = self.needle.ptr,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const SearchTotal = struct {
|
||||
total: ?usize,
|
||||
|
||||
// Sync with: ghostty_action_search_total_s
|
||||
pub const C = extern struct {
|
||||
total: isize,
|
||||
};
|
||||
|
||||
pub fn cval(self: SearchTotal) C {
|
||||
return .{
|
||||
.total = if (self.total) |t| @intCast(t) else -1,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const SearchSelected = struct {
|
||||
selected: ?usize,
|
||||
|
||||
// Sync with: ghostty_action_search_selected_s
|
||||
pub const C = extern struct {
|
||||
selected: isize,
|
||||
};
|
||||
|
||||
pub fn cval(self: SearchSelected) C {
|
||||
return .{
|
||||
.selected = if (self.selected) |s| @intCast(s) else -1,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const objc = @import("objc");
|
||||
const apprt = @import("../apprt.zig");
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
const internal_os = @import("../os/main.zig");
|
||||
|
||||
// The required comptime API for any apprt.
|
||||
pub const App = @import("gtk/App.zig");
|
||||
pub const Surface = @import("gtk/Surface.zig");
|
||||
|
||||
@@ -5,18 +5,13 @@ const App = @This();
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const adw = @import("adw");
|
||||
const gio = @import("gio");
|
||||
const apprt = @import("../../apprt.zig");
|
||||
const configpkg = @import("../../config.zig");
|
||||
const internal_os = @import("../../os/main.zig");
|
||||
const Config = configpkg.Config;
|
||||
const CoreApp = @import("../../App.zig");
|
||||
|
||||
const Application = @import("class/application.zig").Application;
|
||||
const Surface = @import("Surface.zig");
|
||||
const gtk_version = @import("gtk_version.zig");
|
||||
const adw_version = @import("adw_version.zig");
|
||||
const ipcNewWindow = @import("ipc/new_window.zig").newWindow;
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
@@ -11,6 +11,20 @@ pub const c = @cImport({
|
||||
@cInclude("adwaita.h");
|
||||
});
|
||||
|
||||
pub const blueprint_compiler_help =
|
||||
\\
|
||||
\\When building from a Git checkout, Ghostty requires
|
||||
\\version {f} or newer of `blueprint-compiler` as a
|
||||
\\build-time dependency. Please install it, ensure that it
|
||||
\\is available on your PATH, and then retry building Ghostty.
|
||||
\\See `HACKING.md` for more details.
|
||||
\\
|
||||
\\This message should *not* appear for normal users, who
|
||||
\\should build Ghostty from official release tarballs instead.
|
||||
\\Please consult https://ghostty.org/docs/install/build for
|
||||
\\more information on the recommended build instructions.
|
||||
;
|
||||
|
||||
const adwaita_version = std.SemanticVersion{
|
||||
.major = c.ADW_MAJOR_VERSION,
|
||||
.minor = c.ADW_MINOR_VERSION,
|
||||
@@ -79,13 +93,9 @@ pub fn main() !void {
|
||||
error.FileNotFound => {
|
||||
std.debug.print(
|
||||
\\`blueprint-compiler` not found.
|
||||
\\
|
||||
\\Ghostty requires version {f} or newer of
|
||||
\\`blueprint-compiler` as a build-time dependency starting
|
||||
\\from version 1.2. Please install it, ensure that it is
|
||||
\\available on your PATH, and then retry building Ghostty.
|
||||
\\
|
||||
, .{required_blueprint_version});
|
||||
++ blueprint_compiler_help,
|
||||
.{required_blueprint_version},
|
||||
);
|
||||
std.posix.exit(1);
|
||||
},
|
||||
else => return err,
|
||||
@@ -103,13 +113,9 @@ pub fn main() !void {
|
||||
if (version.order(required_blueprint_version) == .lt) {
|
||||
std.debug.print(
|
||||
\\`blueprint-compiler` is the wrong version.
|
||||
\\
|
||||
\\Ghostty requires version {f} or newer of
|
||||
\\`blueprint-compiler` as a build-time dependency starting
|
||||
\\from version 1.2. Please install it, ensure that it is
|
||||
\\available on your PATH, and then retry building Ghostty.
|
||||
\\
|
||||
, .{required_blueprint_version});
|
||||
++ blueprint_compiler_help,
|
||||
.{required_blueprint_version},
|
||||
);
|
||||
std.posix.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -144,13 +150,9 @@ pub fn main() !void {
|
||||
error.FileNotFound => {
|
||||
std.debug.print(
|
||||
\\`blueprint-compiler` not found.
|
||||
\\
|
||||
\\Ghostty requires version {f} or newer of
|
||||
\\`blueprint-compiler` as a build-time dependency starting
|
||||
\\from version 1.2. Please install it, ensure that it is
|
||||
\\available on your PATH, and then retry building Ghostty.
|
||||
\\
|
||||
, .{required_blueprint_version});
|
||||
++ blueprint_compiler_help,
|
||||
.{required_blueprint_version},
|
||||
);
|
||||
std.posix.exit(1);
|
||||
},
|
||||
else => return err,
|
||||
|
||||
@@ -43,6 +43,7 @@ pub const blueprints: []const Blueprint = &.{
|
||||
.{ .major = 1, .minor = 5, .name = "inspector-widget" },
|
||||
.{ .major = 1, .minor = 5, .name = "inspector-window" },
|
||||
.{ .major = 1, .minor = 2, .name = "resize-overlay" },
|
||||
.{ .major = 1, .minor = 2, .name = "search-overlay" },
|
||||
.{ .major = 1, .minor = 5, .name = "split-tree" },
|
||||
.{ .major = 1, .minor = 5, .name = "split-tree-split" },
|
||||
.{ .major = 1, .minor = 2, .name = "surface" },
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
/// Contains all the logic for putting the Ghostty process and
|
||||
/// each individual surface into its own cgroup.
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
|
||||
const App = @import("App.zig");
|
||||
const internal_os = @import("../../os/main.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk_systemd_cgroup);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const builtin = @import("builtin");
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gio = @import("gio");
|
||||
@@ -9,7 +8,6 @@ const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
const i18n = @import("../../../os/main.zig").i18n;
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const cgroup = @import("../cgroup.zig");
|
||||
@@ -729,6 +727,11 @@ pub const Application = extern struct {
|
||||
.show_on_screen_keyboard => return Action.showOnScreenKeyboard(target),
|
||||
.command_finished => return Action.commandFinished(target, value),
|
||||
|
||||
.start_search => Action.startSearch(target),
|
||||
.end_search => Action.endSearch(target),
|
||||
.search_total => Action.searchTotal(target, value),
|
||||
.search_selected => Action.searchSelected(target, value),
|
||||
|
||||
// Unimplemented
|
||||
.secure_input,
|
||||
.close_all_windows,
|
||||
@@ -2339,6 +2342,34 @@ const Action = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn startSearch(target: apprt.Target) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| v.rt_surface.surface.setSearchActive(true),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn endSearch(target: apprt.Target) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| v.rt_surface.surface.setSearchActive(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn searchTotal(target: apprt.Target, value: apprt.action.SearchTotal) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| v.rt_surface.surface.setSearchTotal(value.total),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn searchSelected(target: apprt.Target, value: apprt.action.SearchSelected) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| v.rt_surface.surface.setSearchSelected(value.selected),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setTitle(
|
||||
target: apprt.Target,
|
||||
value: apprt.action.SetTitle,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const adw = @import("adw");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
const std = @import("std");
|
||||
const adw = @import("adw");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const i18n = @import("../../../os/main.zig").i18n;
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Dialog = @import("dialog.zig").Dialog;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_close_confirmation_dialog);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const adw = @import("adw");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
const std = @import("std");
|
||||
const adw = @import("adw");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Dialog = @import("dialog.zig").Dialog;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
const std = @import("std");
|
||||
const adw = @import("adw");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
|
||||
@@ -3,10 +3,8 @@ const adw = @import("adw");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_dialog);
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const adw = @import("adw");
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const Binding = @import("../../../input.zig").Binding;
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const key = @import("../key.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Application = @import("application.zig").Application;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
|
||||
const cimgui = @import("cimgui");
|
||||
const gl = @import("opengl");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const std = @import("std");
|
||||
|
||||
const adw = @import("adw");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
|
||||
@@ -2,15 +2,12 @@ const std = @import("std");
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
|
||||
const key = @import("../key.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Application = @import("application.zig").Application;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
const DebugWarning = @import("debug_warning.zig").DebugWarning;
|
||||
const InspectorWidget = @import("inspector_widget.zig").InspectorWidget;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const adw = @import("adw");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
|
||||
486
src/apprt/gtk/class/search_overlay.zig
Normal file
486
src/apprt/gtk/class/search_overlay.zig
Normal file
@@ -0,0 +1,486 @@
|
||||
const std = @import("std");
|
||||
const adw = @import("adw");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gdk = @import("gdk");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_search_overlay);
|
||||
|
||||
/// The overlay that shows the current size while a surface is resizing.
|
||||
/// This can be used generically to show pretty much anything with a
|
||||
/// disappearing overlay, but we have no other use at this point so it
|
||||
/// is named specifically for what it does.
|
||||
///
|
||||
/// General usage:
|
||||
///
|
||||
/// 1. Add it to an overlay
|
||||
/// 2. Set the label with `setLabel`
|
||||
/// 3. Schedule to show it with `schedule`
|
||||
///
|
||||
/// Set any properties to change the behavior.
|
||||
pub const SearchOverlay = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = adw.Bin;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttySearchOverlay",
|
||||
.instanceInit = &init,
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {
|
||||
pub const active = struct {
|
||||
pub const name = "active";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.default = false,
|
||||
.accessor = gobject.ext.typedAccessor(
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.getter = getSearchActive,
|
||||
.setter = setSearchActive,
|
||||
},
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"search-total" = struct {
|
||||
pub const name = "search-total";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
u64,
|
||||
.{
|
||||
.default = 0,
|
||||
.minimum = 0,
|
||||
.maximum = std.math.maxInt(u64),
|
||||
.accessor = gobject.ext.typedAccessor(
|
||||
Self,
|
||||
u64,
|
||||
.{ .getter = getSearchTotal },
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"has-search-total" = struct {
|
||||
pub const name = "has-search-total";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.default = false,
|
||||
.accessor = gobject.ext.typedAccessor(
|
||||
Self,
|
||||
bool,
|
||||
.{ .getter = getHasSearchTotal },
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"search-selected" = struct {
|
||||
pub const name = "search-selected";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
u64,
|
||||
.{
|
||||
.default = 0,
|
||||
.minimum = 0,
|
||||
.maximum = std.math.maxInt(u64),
|
||||
.accessor = gobject.ext.typedAccessor(
|
||||
Self,
|
||||
u64,
|
||||
.{ .getter = getSearchSelected },
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"has-search-selected" = struct {
|
||||
pub const name = "has-search-selected";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.default = false,
|
||||
.accessor = gobject.ext.typedAccessor(
|
||||
Self,
|
||||
bool,
|
||||
.{ .getter = getHasSearchSelected },
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"halign-target" = struct {
|
||||
pub const name = "halign-target";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
gtk.Align,
|
||||
.{
|
||||
.default = .end,
|
||||
.accessor = C.privateShallowFieldAccessor("halign_target"),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"valign-target" = struct {
|
||||
pub const name = "valign-target";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
gtk.Align,
|
||||
.{
|
||||
.default = .start,
|
||||
.accessor = C.privateShallowFieldAccessor("valign_target"),
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
pub const signals = struct {
|
||||
/// Emitted when the search is stopped (e.g., Escape pressed).
|
||||
pub const @"stop-search" = struct {
|
||||
pub const name = "stop-search";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
|
||||
/// Emitted when the search text changes (debounced).
|
||||
pub const @"search-changed" = struct {
|
||||
pub const name = "search-changed";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{?[*:0]const u8},
|
||||
void,
|
||||
);
|
||||
};
|
||||
|
||||
/// Emitted when navigating to the next match.
|
||||
pub const @"next-match" = struct {
|
||||
pub const name = "next-match";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
|
||||
/// Emitted when navigating to the previous match.
|
||||
pub const @"previous-match" = struct {
|
||||
pub const name = "previous-match";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Private = struct {
|
||||
/// The search entry widget.
|
||||
search_entry: *gtk.SearchEntry,
|
||||
|
||||
/// True when a search is active, meaning we should show the overlay.
|
||||
active: bool = false,
|
||||
|
||||
/// Total number of search matches (null means unknown/none).
|
||||
search_total: ?usize = null,
|
||||
|
||||
/// Currently selected match index (null means none selected).
|
||||
search_selected: ?usize = null,
|
||||
|
||||
/// Target horizontal alignment for the overlay.
|
||||
halign_target: gtk.Align = .end,
|
||||
|
||||
/// Target vertical alignment for the overlay.
|
||||
valign_target: gtk.Align = .start,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||
}
|
||||
|
||||
/// Grab focus on the search entry and select all text.
|
||||
pub fn grabFocus(self: *Self) void {
|
||||
const priv = self.private();
|
||||
_ = priv.search_entry.as(gtk.Widget).grabFocus();
|
||||
|
||||
// Select all text in the search entry field. -1 is distance from
|
||||
// the end, causing the entire text to be selected.
|
||||
priv.search_entry.as(gtk.Editable).selectRegion(0, -1);
|
||||
}
|
||||
|
||||
// Set active status, and update search on activation
|
||||
fn setSearchActive(self: *Self, active: bool) void {
|
||||
const priv = self.private();
|
||||
if (!priv.active and active) {
|
||||
const text = priv.search_entry.as(gtk.Editable).getText();
|
||||
signals.@"search-changed".impl.emit(self, null, .{text}, null);
|
||||
}
|
||||
priv.active = active;
|
||||
}
|
||||
|
||||
/// Set the total number of search matches.
|
||||
pub fn setSearchTotal(self: *Self, total: ?usize) void {
|
||||
const priv = self.private();
|
||||
const had_total = priv.search_total != null;
|
||||
if (priv.search_total == total) return;
|
||||
priv.search_total = total;
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"search-total".impl.param_spec);
|
||||
if (had_total != (total != null)) {
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"has-search-total".impl.param_spec);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the currently selected match index.
|
||||
pub fn setSearchSelected(self: *Self, selected: ?usize) void {
|
||||
const priv = self.private();
|
||||
const had_selected = priv.search_selected != null;
|
||||
if (priv.search_selected == selected) return;
|
||||
priv.search_selected = selected;
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"search-selected".impl.param_spec);
|
||||
if (had_selected != (selected != null)) {
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"has-search-selected".impl.param_spec);
|
||||
}
|
||||
}
|
||||
|
||||
fn getSearchActive(self: *Self) bool {
|
||||
return self.private().active;
|
||||
}
|
||||
|
||||
fn getSearchTotal(self: *Self) u64 {
|
||||
return self.private().search_total orelse 0;
|
||||
}
|
||||
|
||||
fn getHasSearchTotal(self: *Self) bool {
|
||||
return self.private().search_total != null;
|
||||
}
|
||||
|
||||
fn getSearchSelected(self: *Self) u64 {
|
||||
return self.private().search_selected orelse 0;
|
||||
}
|
||||
|
||||
fn getHasSearchSelected(self: *Self) bool {
|
||||
return self.private().search_selected != null;
|
||||
}
|
||||
|
||||
fn closureMatchLabel(
|
||||
_: *Self,
|
||||
has_selected: bool,
|
||||
selected: u64,
|
||||
has_total: bool,
|
||||
total: u64,
|
||||
) callconv(.c) ?[*:0]const u8 {
|
||||
if (!has_total or total == 0) return glib.ext.dupeZ(u8, "0/0");
|
||||
var buf: [32]u8 = undefined;
|
||||
const label = std.fmt.bufPrintZ(&buf, "{}/{}", .{
|
||||
if (has_selected) selected + 1 else 0,
|
||||
total,
|
||||
}) catch return null;
|
||||
return glib.ext.dupeZ(u8, label);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Template callbacks
|
||||
|
||||
fn searchChanged(entry: *gtk.SearchEntry, self: *Self) callconv(.c) void {
|
||||
const text = entry.as(gtk.Editable).getText();
|
||||
signals.@"search-changed".impl.emit(self, null, .{text}, null);
|
||||
}
|
||||
|
||||
// NOTE: The callbacks below use anyopaque for the first parameter
|
||||
// because they're shared with multiple widgets in the template.
|
||||
|
||||
fn stopSearch(_: *anyopaque, self: *Self) callconv(.c) void {
|
||||
signals.@"stop-search".impl.emit(self, null, .{}, null);
|
||||
}
|
||||
|
||||
fn nextMatch(_: *anyopaque, self: *Self) callconv(.c) void {
|
||||
signals.@"next-match".impl.emit(self, null, .{}, null);
|
||||
}
|
||||
|
||||
fn previousMatch(_: *anyopaque, self: *Self) callconv(.c) void {
|
||||
signals.@"previous-match".impl.emit(self, null, .{}, null);
|
||||
}
|
||||
|
||||
fn searchEntryKeyPressed(
|
||||
_: *gtk.EventControllerKey,
|
||||
keyval: c_uint,
|
||||
_: c_uint,
|
||||
gtk_mods: gdk.ModifierType,
|
||||
self: *Self,
|
||||
) callconv(.c) c_int {
|
||||
if (keyval == gdk.KEY_Return or keyval == gdk.KEY_KP_Enter) {
|
||||
if (gtk_mods.shift_mask) {
|
||||
signals.@"previous-match".impl.emit(self, null, .{}, null);
|
||||
} else {
|
||||
signals.@"next-match".impl.emit(self, null, .{}, null);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn onDragEnd(
|
||||
_: *gtk.GestureDrag,
|
||||
offset_x: f64,
|
||||
offset_y: f64,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
// On drag end, we want to move our halign/valign if we crossed
|
||||
// the midpoint on either axis. This lets the search overlay be
|
||||
// moved to different corners of the parent container.
|
||||
|
||||
const priv = self.private();
|
||||
const widget = self.as(gtk.Widget);
|
||||
const parent = widget.getParent() orelse return;
|
||||
|
||||
const parent_width: f64 = @floatFromInt(parent.getAllocatedWidth());
|
||||
const parent_height: f64 = @floatFromInt(parent.getAllocatedHeight());
|
||||
const self_width: f64 = @floatFromInt(widget.getAllocatedWidth());
|
||||
const self_height: f64 = @floatFromInt(widget.getAllocatedHeight());
|
||||
|
||||
const self_x: f64 = if (priv.halign_target == .start) 0 else parent_width - self_width;
|
||||
const self_y: f64 = if (priv.valign_target == .start) 0 else parent_height - self_height;
|
||||
|
||||
const new_x = self_x + offset_x + (self_width / 2);
|
||||
const new_y = self_y + offset_y + (self_height / 2);
|
||||
|
||||
const new_halign: gtk.Align = if (new_x > parent_width / 2) .end else .start;
|
||||
const new_valign: gtk.Align = if (new_y > parent_height / 2) .end else .start;
|
||||
|
||||
var changed = false;
|
||||
if (new_halign != priv.halign_target) {
|
||||
priv.halign_target = new_halign;
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"halign-target".impl.param_spec);
|
||||
changed = true;
|
||||
}
|
||||
if (new_valign != priv.valign_target) {
|
||||
priv.valign_target = new_valign;
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"valign-target".impl.param_spec);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) self.as(gtk.Widget).queueResize();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual methods
|
||||
|
||||
fn dispose(self: *Self) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
_ = priv;
|
||||
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
);
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
fn finalize(self: *Self) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
_ = priv;
|
||||
|
||||
gobject.Object.virtual_methods.finalize.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.c) void {
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
.major = 1,
|
||||
.minor = 2,
|
||||
.name = "search-overlay",
|
||||
}),
|
||||
);
|
||||
|
||||
// Bindings
|
||||
class.bindTemplateChildPrivate("search_entry", .{});
|
||||
|
||||
// Template Callbacks
|
||||
class.bindTemplateCallback("stop_search", &stopSearch);
|
||||
class.bindTemplateCallback("search_changed", &searchChanged);
|
||||
class.bindTemplateCallback("match_label_closure", &closureMatchLabel);
|
||||
class.bindTemplateCallback("next_match", &nextMatch);
|
||||
class.bindTemplateCallback("previous_match", &previousMatch);
|
||||
class.bindTemplateCallback("search_entry_key_pressed", &searchEntryKeyPressed);
|
||||
class.bindTemplateCallback("on_drag_end", &onDragEnd);
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.active.impl,
|
||||
properties.@"search-total".impl,
|
||||
properties.@"has-search-total".impl,
|
||||
properties.@"search-selected".impl,
|
||||
properties.@"has-search-selected".impl,
|
||||
properties.@"halign-target".impl,
|
||||
properties.@"valign-target".impl,
|
||||
});
|
||||
|
||||
// Signals
|
||||
signals.@"stop-search".impl.register(.{});
|
||||
signals.@"search-changed".impl.register(.{});
|
||||
signals.@"next-match".impl.register(.{});
|
||||
signals.@"previous-match".impl.register(.{});
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
||||
}
|
||||
|
||||
pub const as = C.Class.as;
|
||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
const std = @import("std");
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const adw = @import("adw");
|
||||
const gio = @import("gio");
|
||||
@@ -8,17 +7,11 @@ const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const i18n = @import("../../../os/main.zig").i18n;
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const input = @import("../../../input.zig");
|
||||
const CoreSurface = @import("../../../Surface.zig");
|
||||
const gtk_version = @import("../gtk_version.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const ext = @import("../ext.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Application = @import("application.zig").Application;
|
||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
@@ -19,18 +19,17 @@ const terminal = @import("../../../terminal/main.zig");
|
||||
const CoreSurface = @import("../../../Surface.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const ext = @import("../ext.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const gtk_key = @import("../key.zig");
|
||||
const ApprtSurface = @import("../Surface.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Application = @import("application.zig").Application;
|
||||
const Config = @import("config.zig").Config;
|
||||
const ResizeOverlay = @import("resize_overlay.zig").ResizeOverlay;
|
||||
const SearchOverlay = @import("search_overlay.zig").SearchOverlay;
|
||||
const ChildExited = @import("surface_child_exited.zig").SurfaceChildExited;
|
||||
const ClipboardConfirmationDialog = @import("clipboard_confirmation_dialog.zig").ClipboardConfirmationDialog;
|
||||
const TitleDialog = @import("surface_title_dialog.zig").SurfaceTitleDialog;
|
||||
const Window = @import("window.zig").Window;
|
||||
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||
const InspectorWindow = @import("inspector_window.zig").InspectorWindow;
|
||||
const i18n = @import("../../../os/i18n.zig");
|
||||
|
||||
@@ -551,6 +550,9 @@ pub const Surface = extern struct {
|
||||
/// The resize overlay
|
||||
resize_overlay: *ResizeOverlay,
|
||||
|
||||
/// The search overlay
|
||||
search_overlay: *SearchOverlay,
|
||||
|
||||
/// The apprt Surface.
|
||||
rt_surface: ApprtSurface = undefined,
|
||||
|
||||
@@ -1465,6 +1467,10 @@ pub const Surface = extern struct {
|
||||
// EnvMap is a bit annoying so I'm punting it.
|
||||
if (ext.getAncestor(Window, self.as(gtk.Widget))) |window| {
|
||||
try window.winproto().addSubprocessEnv(&env);
|
||||
|
||||
if (window.isQuickTerminal()) {
|
||||
try env.put("GHOSTTY_QUICK_TERMINAL", "1");
|
||||
}
|
||||
}
|
||||
|
||||
return env;
|
||||
@@ -1949,6 +1955,29 @@ pub const Surface = extern struct {
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"error".impl.param_spec);
|
||||
}
|
||||
|
||||
pub fn setSearchActive(self: *Self, active: bool) void {
|
||||
const priv = self.private();
|
||||
var value = gobject.ext.Value.newFrom(active);
|
||||
defer value.unset();
|
||||
gobject.Object.setProperty(
|
||||
priv.search_overlay.as(gobject.Object),
|
||||
SearchOverlay.properties.active.name,
|
||||
&value,
|
||||
);
|
||||
|
||||
if (active) {
|
||||
priv.search_overlay.grabFocus();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setSearchTotal(self: *Self, total: ?usize) void {
|
||||
self.private().search_overlay.setSearchTotal(total);
|
||||
}
|
||||
|
||||
pub fn setSearchSelected(self: *Self, selected: ?usize) void {
|
||||
self.private().search_overlay.setSearchSelected(selected);
|
||||
}
|
||||
|
||||
fn propConfig(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
@@ -3168,6 +3197,35 @@ pub const Surface = extern struct {
|
||||
self.setTitleOverride(if (title.len == 0) null else title);
|
||||
}
|
||||
|
||||
fn searchStop(_: *SearchOverlay, self: *Self) callconv(.c) void {
|
||||
const surface = self.core() orelse return;
|
||||
_ = surface.performBindingAction(.end_search) catch |err| {
|
||||
log.warn("unable to perform end_search action err={}", .{err});
|
||||
};
|
||||
_ = self.private().gl_area.as(gtk.Widget).grabFocus();
|
||||
}
|
||||
|
||||
fn searchChanged(_: *SearchOverlay, needle: ?[*:0]const u8, self: *Self) callconv(.c) void {
|
||||
const surface = self.core() orelse return;
|
||||
_ = surface.performBindingAction(.{ .search = std.mem.sliceTo(needle orelse "", 0) }) catch |err| {
|
||||
log.warn("unable to perform search action err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
fn searchNextMatch(_: *SearchOverlay, self: *Self) callconv(.c) void {
|
||||
const surface = self.core() orelse return;
|
||||
_ = surface.performBindingAction(.{ .navigate_search = .next }) catch |err| {
|
||||
log.warn("unable to perform navigate_search action err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
fn searchPreviousMatch(_: *SearchOverlay, self: *Self) callconv(.c) void {
|
||||
const surface = self.core() orelse return;
|
||||
_ = surface.performBindingAction(.{ .navigate_search = .previous }) catch |err| {
|
||||
log.warn("unable to perform navigate_search action err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
@@ -3182,6 +3240,7 @@ pub const Surface = extern struct {
|
||||
|
||||
fn init(class: *Class) callconv(.c) void {
|
||||
gobject.ext.ensureType(ResizeOverlay);
|
||||
gobject.ext.ensureType(SearchOverlay);
|
||||
gobject.ext.ensureType(ChildExited);
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
@@ -3201,6 +3260,7 @@ pub const Surface = extern struct {
|
||||
class.bindTemplateChildPrivate("error_page", .{});
|
||||
class.bindTemplateChildPrivate("progress_bar_overlay", .{});
|
||||
class.bindTemplateChildPrivate("resize_overlay", .{});
|
||||
class.bindTemplateChildPrivate("search_overlay", .{});
|
||||
class.bindTemplateChildPrivate("terminal_page", .{});
|
||||
class.bindTemplateChildPrivate("drop_target", .{});
|
||||
class.bindTemplateChildPrivate("im_context", .{});
|
||||
@@ -3238,6 +3298,10 @@ pub const Surface = extern struct {
|
||||
class.bindTemplateCallback("notify_vadjustment", &propVAdjustment);
|
||||
class.bindTemplateCallback("should_border_be_shown", &closureShouldBorderBeShown);
|
||||
class.bindTemplateCallback("should_unfocused_split_be_shown", &closureShouldUnfocusedSplitBeShown);
|
||||
class.bindTemplateCallback("search_stop", &searchStop);
|
||||
class.bindTemplateCallback("search_changed", &searchChanged);
|
||||
class.bindTemplateCallback("search_next_match", &searchNextMatch);
|
||||
class.bindTemplateCallback("search_previous_match", &searchPreviousMatch);
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
@@ -3369,12 +3433,16 @@ const Clipboard = struct {
|
||||
// text/plain type. The default charset when there is
|
||||
// none is ASCII, and lots of things look for UTF-8
|
||||
// specifically.
|
||||
// The specs are not clear about the order here, but
|
||||
// some clients apparently pick the first match in the
|
||||
// order we set here then garble up bare 'text/plain'
|
||||
// with non-ASCII UTF-8 content, so offer UTF-8 first.
|
||||
//
|
||||
// Note that under X11, GTK automatically adds the
|
||||
// UTF8_STRING atom when this is present.
|
||||
const text_provider_atoms = [_][:0]const u8{
|
||||
"text/plain",
|
||||
"text/plain;charset=utf-8",
|
||||
"text/plain",
|
||||
};
|
||||
var text_providers: [text_provider_atoms.len]*gdk.ContentProvider = undefined;
|
||||
for (text_provider_atoms, 0..) |atom, j| {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const adw = @import("adw");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const adw = @import("adw");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
@@ -6,7 +6,6 @@ const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const ext = @import("../ext.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
const std = @import("std");
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
const assert = std.debug.assert;
|
||||
const adw = @import("adw");
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const i18n = @import("../../../os/main.zig").i18n;
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const input = @import("../../../input.zig");
|
||||
const CoreSurface = @import("../../../Surface.zig");
|
||||
const ext = @import("../ext.zig");
|
||||
const gtk_version = @import("../gtk_version.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gio = @import("gio");
|
||||
@@ -28,7 +28,6 @@ const Surface = @import("surface.zig").Surface;
|
||||
const Tab = @import("tab.zig").Tab;
|
||||
const DebugWarning = @import("debug_warning.zig").DebugWarning;
|
||||
const CommandPalette = @import("command_palette.zig").CommandPalette;
|
||||
const InspectorWindow = @import("inspector_window.zig").InspectorWindow;
|
||||
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_window);
|
||||
|
||||
@@ -34,6 +34,18 @@ label.url-overlay.right {
|
||||
border-radius: 6px 0px 0px 0px;
|
||||
}
|
||||
|
||||
/*
|
||||
* GhosttySurface search overlay
|
||||
*/
|
||||
.search-overlay {
|
||||
padding: 6px 8px;
|
||||
margin: 8px;
|
||||
border-radius: 8px;
|
||||
outline-style: solid;
|
||||
outline-color: #555555;
|
||||
outline-width: 1px;
|
||||
}
|
||||
|
||||
/*
|
||||
* GhosttySurface resize overlay
|
||||
*/
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
//! helpers.
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../../quirks.zig").inlineAssert;
|
||||
const testing = std.testing;
|
||||
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../../../quirks.zig").inlineAssert;
|
||||
const testing = std.testing;
|
||||
|
||||
const gio = @import("gio");
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
|
||||
const gdk = @import("gdk");
|
||||
const glib = @import("glib");
|
||||
|
||||
94
src/apprt/gtk/ui/1.2/search-overlay.blp
Normal file
94
src/apprt/gtk/ui/1.2/search-overlay.blp
Normal file
@@ -0,0 +1,94 @@
|
||||
using Gtk 4.0;
|
||||
using Gdk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $GhosttySearchOverlay: Adw.Bin {
|
||||
visible: bind template.active;
|
||||
halign-target: end;
|
||||
valign-target: start;
|
||||
halign: bind template.halign-target;
|
||||
valign: bind template.valign-target;
|
||||
|
||||
GestureDrag {
|
||||
button: 1;
|
||||
propagation-phase: capture;
|
||||
drag-end => $on_drag_end();
|
||||
}
|
||||
|
||||
Adw.Bin {
|
||||
Box container {
|
||||
styles [
|
||||
"background",
|
||||
"search-overlay",
|
||||
]
|
||||
|
||||
orientation: horizontal;
|
||||
spacing: 6;
|
||||
|
||||
SearchEntry search_entry {
|
||||
placeholder-text: _("Find…");
|
||||
width-chars: 20;
|
||||
hexpand: true;
|
||||
stop-search => $stop_search();
|
||||
search-changed => $search_changed();
|
||||
next-match => $next_match();
|
||||
previous-match => $previous_match();
|
||||
|
||||
EventControllerKey {
|
||||
// We need this so we capture before the SearchEntry.
|
||||
propagation-phase: capture;
|
||||
key-pressed => $search_entry_key_pressed();
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
styles [
|
||||
"dim-label",
|
||||
]
|
||||
|
||||
label: bind $match_label_closure(template.has-search-selected, template.search-selected, template.has-search-total, template.search-total) as <string>;
|
||||
width-chars: 6;
|
||||
xalign: 1.0;
|
||||
}
|
||||
|
||||
Box button_box {
|
||||
orientation: horizontal;
|
||||
spacing: 1;
|
||||
|
||||
styles [
|
||||
"linked",
|
||||
]
|
||||
|
||||
Button prev_button {
|
||||
icon-name: "go-up-symbolic";
|
||||
tooltip-text: _("Previous Match");
|
||||
clicked => $next_match();
|
||||
|
||||
cursor: Gdk.Cursor {
|
||||
name: "pointer";
|
||||
};
|
||||
}
|
||||
|
||||
Button next_button {
|
||||
icon-name: "go-down-symbolic";
|
||||
tooltip-text: _("Next Match");
|
||||
clicked => $previous_match();
|
||||
|
||||
cursor: Gdk.Cursor {
|
||||
name: "pointer";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Button close_button {
|
||||
icon-name: "window-close-symbolic";
|
||||
tooltip-text: _("Close");
|
||||
clicked => $stop_search();
|
||||
|
||||
cursor: Gdk.Cursor {
|
||||
name: "pointer";
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,34 @@ Overlay terminal_page {
|
||||
halign: start;
|
||||
has-arrow: false;
|
||||
}
|
||||
|
||||
EventControllerFocus {
|
||||
enter => $focus_enter();
|
||||
leave => $focus_leave();
|
||||
}
|
||||
|
||||
EventControllerKey {
|
||||
key-pressed => $key_pressed();
|
||||
key-released => $key_released();
|
||||
}
|
||||
|
||||
EventControllerScroll {
|
||||
scroll => $scroll();
|
||||
scroll-begin => $scroll_begin();
|
||||
scroll-end => $scroll_end();
|
||||
flags: both_axes;
|
||||
}
|
||||
|
||||
EventControllerMotion {
|
||||
motion => $mouse_motion();
|
||||
leave => $mouse_leave();
|
||||
}
|
||||
|
||||
GestureClick {
|
||||
pressed => $mouse_down();
|
||||
released => $mouse_up();
|
||||
button: 0;
|
||||
}
|
||||
};
|
||||
|
||||
[overlay]
|
||||
@@ -64,6 +92,10 @@ Overlay terminal_page {
|
||||
reveal-child: bind $should_border_be_shown(template.config, template.bell-ringing) as <bool>;
|
||||
transition-type: crossfade;
|
||||
transition-duration: 500;
|
||||
// Revealers take up the full size, we need this to not capture events.
|
||||
can-focus: false;
|
||||
can-target: false;
|
||||
focusable: false;
|
||||
|
||||
Box bell_overlay {
|
||||
styles [
|
||||
@@ -115,12 +147,26 @@ Overlay terminal_page {
|
||||
label: bind template.mouse-hover-url;
|
||||
}
|
||||
|
||||
[overlay]
|
||||
$GhosttySearchOverlay search_overlay {
|
||||
stop-search => $search_stop();
|
||||
search-changed => $search_changed();
|
||||
next-match => $search_next_match();
|
||||
previous-match => $search_previous_match();
|
||||
}
|
||||
|
||||
[overlay]
|
||||
// Apply unfocused-split-fill and unfocused-split-opacity to current surface
|
||||
// this is only applied when a tab has more than one surface
|
||||
Revealer {
|
||||
reveal-child: bind $should_unfocused_split_be_shown(template.focused, template.is-split) as <bool>;
|
||||
transition-duration: 0;
|
||||
// This is all necessary so that the Revealer itself doesn't override
|
||||
// any input events from the other overlays. Namely, if you don't have
|
||||
// these then the search overlay won't get mouse events.
|
||||
can-focus: false;
|
||||
can-target: false;
|
||||
focusable: false;
|
||||
|
||||
DrawingArea {
|
||||
styles [
|
||||
@@ -129,35 +175,6 @@ Overlay terminal_page {
|
||||
}
|
||||
}
|
||||
|
||||
// Event controllers for interactivity
|
||||
EventControllerFocus {
|
||||
enter => $focus_enter();
|
||||
leave => $focus_leave();
|
||||
}
|
||||
|
||||
EventControllerKey {
|
||||
key-pressed => $key_pressed();
|
||||
key-released => $key_released();
|
||||
}
|
||||
|
||||
EventControllerMotion {
|
||||
motion => $mouse_motion();
|
||||
leave => $mouse_leave();
|
||||
}
|
||||
|
||||
EventControllerScroll {
|
||||
scroll => $scroll();
|
||||
scroll-begin => $scroll_begin();
|
||||
scroll-end => $scroll_end();
|
||||
flags: both_axes;
|
||||
}
|
||||
|
||||
GestureClick {
|
||||
pressed => $mouse_down();
|
||||
released => $mouse_up();
|
||||
button: 0;
|
||||
}
|
||||
|
||||
DropTarget drop_target {
|
||||
drop => $drop();
|
||||
actions: copy;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Wayland protocol implementation for the Ghostty GTK apprt.
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const build_options = @import("build_options");
|
||||
|
||||
const gdk = @import("gdk");
|
||||
const gdk_wayland = @import("gdk_wayland");
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
//! X11 window protocol implementation for the Ghostty GTK apprt.
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const build_options = @import("build_options");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gdk_x11 = @import("gdk_x11");
|
||||
const glib = @import("glib");
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! process.
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
|
||||
pub const Errors = error{
|
||||
/// The IPC failed. If a function returns this error, it's expected that
|
||||
|
||||
@@ -6,15 +6,15 @@ const build_config = @import("../build_config.zig");
|
||||
const App = @import("../App.zig");
|
||||
const Surface = @import("../Surface.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const termio = @import("../termio.zig");
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
const Config = @import("../config.zig").Config;
|
||||
const MessageData = @import("../datastruct/main.zig").MessageData;
|
||||
|
||||
/// The message types that can be sent to a single surface.
|
||||
pub const Message = union(enum) {
|
||||
/// Represents a write request. Magic number comes from the max size
|
||||
/// we want this union to be.
|
||||
pub const WriteReq = termio.MessageData(u8, 255);
|
||||
pub const WriteReq = MessageData(u8, 255);
|
||||
|
||||
/// Set the title of the surface.
|
||||
/// TODO: we should change this to a "WriteReq" style structure in
|
||||
@@ -107,6 +107,12 @@ pub const Message = union(enum) {
|
||||
/// The scrollbar state changed for the surface.
|
||||
scrollbar: terminal.Scrollbar,
|
||||
|
||||
/// Search progress update
|
||||
search_total: ?usize,
|
||||
|
||||
/// Selected search index change
|
||||
search_selected: ?usize,
|
||||
|
||||
pub const ReportTitleStyle = enum {
|
||||
csi_21_t,
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ fn stepWcwidth(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const f = self.data_f orelse return;
|
||||
var read_buf: [4096]u8 = undefined;
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = f.reader(&read_buf);
|
||||
var r = &f_reader.interface;
|
||||
|
||||
@@ -134,7 +134,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const f = self.data_f orelse return;
|
||||
var read_buf: [4096]u8 = undefined;
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = f.reader(&read_buf);
|
||||
var r = &f_reader.interface;
|
||||
|
||||
@@ -166,7 +166,7 @@ fn stepSimd(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const f = self.data_f orelse return;
|
||||
var read_buf: [4096]u8 = undefined;
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = f.reader(&read_buf);
|
||||
var r = &f_reader.interface;
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ fn stepNoop(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const f = self.data_f orelse return;
|
||||
var read_buf: [4096]u8 = undefined;
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = f.reader(&read_buf);
|
||||
var r = &f_reader.interface;
|
||||
|
||||
@@ -113,7 +113,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const f = self.data_f orelse return;
|
||||
var read_buf: [4096]u8 = undefined;
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = f.reader(&read_buf);
|
||||
var r = &f_reader.interface;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
const IsSymbol = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Benchmark = @import("Benchmark.zig");
|
||||
@@ -90,7 +89,7 @@ fn stepUucode(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *IsSymbol = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const f = self.data_f orelse return;
|
||||
var read_buf: [4096]u8 = undefined;
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = f.reader(&read_buf);
|
||||
var r = &f_reader.interface;
|
||||
|
||||
@@ -117,7 +116,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *IsSymbol = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const f = self.data_f orelse return;
|
||||
var read_buf: [4096]u8 = undefined;
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = f.reader(&read_buf);
|
||||
var r = &f_reader.interface;
|
||||
|
||||
|
||||
196
src/benchmark/ScreenClone.zig
Normal file
196
src/benchmark/ScreenClone.zig
Normal file
@@ -0,0 +1,196 @@
|
||||
//! This benchmark tests the performance of the Screen.clone
|
||||
//! function. This is useful because it is one of the primary lock
|
||||
//! holders that impact IO performance when the renderer is active.
|
||||
//! We do this very frequently.
|
||||
const ScreenClone = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const terminalpkg = @import("../terminal/main.zig");
|
||||
const Benchmark = @import("Benchmark.zig");
|
||||
const options = @import("options.zig");
|
||||
const Terminal = terminalpkg.Terminal;
|
||||
|
||||
const log = std.log.scoped(.@"terminal-stream-bench");
|
||||
|
||||
opts: Options,
|
||||
terminal: Terminal,
|
||||
|
||||
pub const Options = struct {
|
||||
/// The type of codepoint width calculation to use.
|
||||
mode: Mode = .clone,
|
||||
|
||||
/// The size of the terminal. This affects benchmarking when
|
||||
/// dealing with soft line wrapping and the memory impact
|
||||
/// of page sizes.
|
||||
@"terminal-rows": u16 = 80,
|
||||
@"terminal-cols": u16 = 120,
|
||||
|
||||
/// The data to read as a filepath. If this is "-" then
|
||||
/// we will read stdin. If this is unset, then we will
|
||||
/// do nothing (benchmark is a noop). It'd be more unixy to
|
||||
/// use stdin by default but I find that a hanging CLI command
|
||||
/// with no interaction is a bit annoying.
|
||||
///
|
||||
/// This will be used to initialize the terminal screen state before
|
||||
/// cloning. This data can switch to alt screen if it wants. The time
|
||||
/// to read this is not part of the benchmark.
|
||||
data: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const Mode = enum {
|
||||
/// The baseline mode copies the screen by value.
|
||||
noop,
|
||||
|
||||
/// Full clone
|
||||
clone,
|
||||
|
||||
/// RenderState rather than a screen clone.
|
||||
render,
|
||||
};
|
||||
|
||||
pub fn create(
|
||||
alloc: Allocator,
|
||||
opts: Options,
|
||||
) !*ScreenClone {
|
||||
const ptr = try alloc.create(ScreenClone);
|
||||
errdefer alloc.destroy(ptr);
|
||||
|
||||
ptr.* = .{
|
||||
.opts = opts,
|
||||
.terminal = try .init(alloc, .{
|
||||
.rows = opts.@"terminal-rows",
|
||||
.cols = opts.@"terminal-cols",
|
||||
}),
|
||||
};
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
pub fn destroy(self: *ScreenClone, alloc: Allocator) void {
|
||||
self.terminal.deinit(alloc);
|
||||
alloc.destroy(self);
|
||||
}
|
||||
|
||||
pub fn benchmark(self: *ScreenClone) Benchmark {
|
||||
return .init(self, .{
|
||||
.stepFn = switch (self.opts.mode) {
|
||||
.noop => stepNoop,
|
||||
.clone => stepClone,
|
||||
.render => stepRender,
|
||||
},
|
||||
.setupFn = setup,
|
||||
.teardownFn = teardown,
|
||||
});
|
||||
}
|
||||
|
||||
fn setup(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *ScreenClone = @ptrCast(@alignCast(ptr));
|
||||
|
||||
// Always reset our terminal state
|
||||
self.terminal.fullReset();
|
||||
|
||||
// Force a style on every single row, which
|
||||
var s = self.terminal.vtStream();
|
||||
defer s.deinit();
|
||||
s.nextSlice("\x1b[48;2;20;40;60m") catch unreachable;
|
||||
for (0..self.terminal.rows - 1) |_| s.nextSlice("hello\r\n") catch unreachable;
|
||||
s.nextSlice("hello") catch unreachable;
|
||||
|
||||
// Setup our terminal state
|
||||
const data_f: std.fs.File = (options.dataFile(
|
||||
self.opts.data,
|
||||
) catch |err| {
|
||||
log.warn("error opening data file err={}", .{err});
|
||||
return error.BenchmarkFailed;
|
||||
}) orelse return;
|
||||
|
||||
var stream = self.terminal.vtStream();
|
||||
defer stream.deinit();
|
||||
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = data_f.reader(&read_buf);
|
||||
const r = &f_reader.interface;
|
||||
|
||||
var buf: [4096]u8 = undefined;
|
||||
while (true) {
|
||||
const n = r.readSliceShort(&buf) catch {
|
||||
log.warn("error reading data file err={?}", .{f_reader.err});
|
||||
return error.BenchmarkFailed;
|
||||
};
|
||||
if (n == 0) break; // EOF reached
|
||||
stream.nextSlice(buf[0..n]) catch |err| {
|
||||
log.warn("error processing data file chunk err={}", .{err});
|
||||
return error.BenchmarkFailed;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn teardown(ptr: *anyopaque) void {
|
||||
const self: *ScreenClone = @ptrCast(@alignCast(ptr));
|
||||
_ = self;
|
||||
}
|
||||
|
||||
fn stepNoop(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *ScreenClone = @ptrCast(@alignCast(ptr));
|
||||
|
||||
// We loop because its so fast that a single benchmark run doesn't
|
||||
// properly capture our speeds.
|
||||
for (0..1000) |_| {
|
||||
const s: terminalpkg.Screen = self.terminal.screens.active.*;
|
||||
std.mem.doNotOptimizeAway(s);
|
||||
}
|
||||
}
|
||||
|
||||
fn stepClone(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *ScreenClone = @ptrCast(@alignCast(ptr));
|
||||
|
||||
// We loop because its so fast that a single benchmark run doesn't
|
||||
// properly capture our speeds.
|
||||
for (0..1000) |_| {
|
||||
const s: *terminalpkg.Screen = self.terminal.screens.active;
|
||||
const copy = s.clone(
|
||||
s.alloc,
|
||||
.{ .viewport = .{} },
|
||||
null,
|
||||
) catch |err| {
|
||||
log.warn("error cloning screen err={}", .{err});
|
||||
return error.BenchmarkFailed;
|
||||
};
|
||||
std.mem.doNotOptimizeAway(copy);
|
||||
|
||||
// Note: we purposely do not free memory because we don't want
|
||||
// to benchmark that. We'll free when the benchmark exits.
|
||||
}
|
||||
}
|
||||
|
||||
fn stepRender(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *ScreenClone = @ptrCast(@alignCast(ptr));
|
||||
|
||||
// We do this once out of the loop because a significant slowdown
|
||||
// on the first run is allocation. After that first run, even with
|
||||
// a full rebuild, it is much faster. Let's ignore that first run
|
||||
// slowdown.
|
||||
const alloc = self.terminal.screens.active.alloc;
|
||||
var state: terminalpkg.RenderState = .empty;
|
||||
state.update(alloc, &self.terminal) catch |err| {
|
||||
log.warn("error cloning screen err={}", .{err});
|
||||
return error.BenchmarkFailed;
|
||||
};
|
||||
|
||||
// We loop because its so fast that a single benchmark run doesn't
|
||||
// properly capture our speeds.
|
||||
for (0..1000) |_| {
|
||||
// Forces a full rebuild because it thinks our screen changed
|
||||
state.screen = .alternate;
|
||||
state.update(alloc, &self.terminal) catch |err| {
|
||||
log.warn("error cloning screen err={}", .{err});
|
||||
return error.BenchmarkFailed;
|
||||
};
|
||||
std.mem.doNotOptimizeAway(state);
|
||||
|
||||
// Note: we purposely do not free memory because we don't want
|
||||
// to benchmark that. We'll free when the benchmark exits.
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
|
||||
// the benchmark results and... I know writing this that we
|
||||
// aren't currently IO bound.
|
||||
const f = self.data_f orelse return;
|
||||
var read_buf: [4096]u8 = undefined;
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = f.reader(&read_buf);
|
||||
var r = &f_reader.interface;
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
|
||||
// aren't currently IO bound.
|
||||
const f = self.data_f orelse return;
|
||||
|
||||
var read_buf: [4096]u8 = undefined;
|
||||
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
|
||||
var f_reader = f.reader(&read_buf);
|
||||
const r = &f_reader.interface;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ const cli = @import("../cli.zig");
|
||||
pub const Action = enum {
|
||||
@"codepoint-width",
|
||||
@"grapheme-break",
|
||||
@"screen-clone",
|
||||
@"terminal-parser",
|
||||
@"terminal-stream",
|
||||
@"is-symbol",
|
||||
@@ -22,6 +23,7 @@ pub const Action = enum {
|
||||
/// See TerminalStream for an example.
|
||||
pub fn Struct(comptime action: Action) type {
|
||||
return switch (action) {
|
||||
.@"screen-clone" => @import("ScreenClone.zig"),
|
||||
.@"terminal-stream" => @import("TerminalStream.zig"),
|
||||
.@"codepoint-width" => @import("CodepointWidth.zig"),
|
||||
.@"grapheme-break" => @import("GraphemeBreak.zig"),
|
||||
|
||||
@@ -4,6 +4,7 @@ pub const CApi = @import("CApi.zig");
|
||||
pub const TerminalStream = @import("TerminalStream.zig");
|
||||
pub const CodepointWidth = @import("CodepointWidth.zig");
|
||||
pub const GraphemeBreak = @import("GraphemeBreak.zig");
|
||||
pub const ScreenClone = @import("ScreenClone.zig");
|
||||
pub const TerminalParser = @import("TerminalParser.zig");
|
||||
pub const IsSymbol = @import("IsSymbol.zig");
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
const GhosttyBench = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Config = @import("Config.zig");
|
||||
const SharedDeps = @import("SharedDeps.zig");
|
||||
|
||||
steps: []*std.Build.Step.Compile,
|
||||
|
||||
@@ -170,11 +170,11 @@ pub const Resource = struct {
|
||||
|
||||
/// Returns true if the dist path exists at build time.
|
||||
pub fn exists(self: *const Resource, b: *std.Build) bool {
|
||||
if (std.fs.accessAbsolute(b.pathFromRoot(self.dist), .{})) {
|
||||
if (b.build_root.handle.access(self.dist, .{})) {
|
||||
// If we have a ".git" directory then we're a git checkout
|
||||
// and we never want to use the dist path. This shouldn't happen
|
||||
// so show a warning to the user.
|
||||
if (std.fs.accessAbsolute(b.pathFromRoot(".git"), .{})) {
|
||||
if (b.build_root.handle.access(".git", .{})) {
|
||||
std.log.warn(
|
||||
"dist resource '{s}' should not be in a git checkout",
|
||||
.{self.dist},
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
const GhosttyFrameData = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Config = @import("Config.zig");
|
||||
const SharedDeps = @import("SharedDeps.zig");
|
||||
const DistResource = @import("GhosttyDist.zig").Resource;
|
||||
|
||||
/// The output path for the compressed framedata zig file
|
||||
|
||||
@@ -3,11 +3,7 @@ const GhosttyLibVt = @This();
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const RunStep = std.Build.Step.Run;
|
||||
const Config = @import("Config.zig");
|
||||
const GhosttyZig = @import("GhosttyZig.zig");
|
||||
const SharedDeps = @import("SharedDeps.zig");
|
||||
const LibtoolStep = @import("LibtoolStep.zig");
|
||||
const LipoStep = @import("LipoStep.zig");
|
||||
|
||||
/// The step that generates the file.
|
||||
step: *std.Build.Step,
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
const GhosttyResources = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const buildpkg = @import("main.zig");
|
||||
const Config = @import("Config.zig");
|
||||
const RunStep = std.Build.Step.Run;
|
||||
const SharedDeps = @import("SharedDeps.zig");
|
||||
|
||||
steps: []*std.Build.Step,
|
||||
|
||||
pub fn init(b: *std.Build, cfg: *const Config) !GhosttyResources {
|
||||
pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !GhosttyResources {
|
||||
var steps: std.ArrayList(*std.Build.Step) = .empty;
|
||||
errdefer steps.deinit(b.allocator);
|
||||
|
||||
@@ -26,6 +25,8 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyResources {
|
||||
});
|
||||
build_data_exe.linkLibC();
|
||||
|
||||
deps.help_strings.addImport(build_data_exe);
|
||||
|
||||
// Terminfo
|
||||
terminfo: {
|
||||
const os_tag = cfg.target.result.os.tag;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
const GhosttyWebdata = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Config = @import("Config.zig");
|
||||
const SharedDeps = @import("SharedDeps.zig");
|
||||
|
||||
steps: []*std.Build.Step,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const UnicodeTables = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Config = @import("Config.zig");
|
||||
|
||||
/// The exe.
|
||||
props_exe: *std.Build.Step.Compile,
|
||||
|
||||
@@ -19,23 +19,23 @@ fn computeWidth(
|
||||
_ = backing;
|
||||
_ = tracking;
|
||||
|
||||
// Emoji modifiers are technically width 0 because they're joining
|
||||
// points. But we handle joining via grapheme break and don't use width
|
||||
// there. If a emoji modifier is standalone, we want it to take up
|
||||
// two columns.
|
||||
if (data.is_emoji_modifier) {
|
||||
assert(data.wcwidth == 0);
|
||||
data.wcwidth = 2;
|
||||
return;
|
||||
// This condition is to get the previous behavior of uucode's `wcwidth`,
|
||||
// returning the width of a code point in a grapheme cluster but with the
|
||||
// exception to treat emoji modifiers as width 2 so they can be displayed
|
||||
// in isolation. PRs to follow will take advantage of the new uucode
|
||||
// `wcwidth_standalone` vs `wcwidth_zero_in_grapheme` split.
|
||||
if (data.wcwidth_zero_in_grapheme and !data.is_emoji_modifier) {
|
||||
data.width = 0;
|
||||
} else {
|
||||
data.width = @min(2, data.wcwidth_standalone);
|
||||
}
|
||||
|
||||
data.width = @intCast(@min(2, @max(0, data.wcwidth)));
|
||||
}
|
||||
|
||||
const width = config.Extension{
|
||||
.inputs = &.{
|
||||
"wcwidth_standalone",
|
||||
"wcwidth_zero_in_grapheme",
|
||||
"is_emoji_modifier",
|
||||
"wcwidth",
|
||||
},
|
||||
.compute = &computeWidth,
|
||||
.fields = &.{
|
||||
@@ -90,10 +90,7 @@ pub const tables = [_]config.Table{
|
||||
width.field("width"),
|
||||
d.field("grapheme_break"),
|
||||
is_symbol.field("is_symbol"),
|
||||
d.field("is_emoji_modifier"),
|
||||
d.field("is_emoji_modifier_base"),
|
||||
d.field("is_emoji_vs_text"),
|
||||
d.field("is_emoji_vs_emoji"),
|
||||
d.field("is_emoji_vs_base"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const std = @import("std");
|
||||
const help_strings = @import("help_strings");
|
||||
const helpgen_actions = @import("../../input/helpgen_actions.zig");
|
||||
|
||||
pub fn main() !void {
|
||||
|
||||
@@ -9,7 +9,6 @@ const assert = std.debug.assert;
|
||||
const apprt = @import("apprt.zig");
|
||||
const font = @import("font/main.zig");
|
||||
const rendererpkg = @import("renderer.zig");
|
||||
const WasmTarget = @import("os/wasm/target.zig").Target;
|
||||
const BuildConfig = @import("build/Config.zig");
|
||||
|
||||
pub const ReleaseChannel = BuildConfig.ReleaseChannel;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const Allocator = mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const diags = @import("diagnostics.zig");
|
||||
|
||||
@@ -3,7 +3,6 @@ const builtin = @import("builtin");
|
||||
const args = @import("args.zig");
|
||||
const Action = @import("ghostty.zig").Action;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const help_strings = @import("help_strings");
|
||||
const vaxis = @import("vaxis");
|
||||
|
||||
const framedata = @import("framedata").compressed;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const build_config = @import("../build_config.zig");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const args = @import("args.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Action = @import("ghostty.zig").Action;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
const std = @import("std");
|
||||
const inputpkg = @import("../input.zig");
|
||||
const args = @import("args.zig");
|
||||
const Action = @import("ghostty.zig").Action;
|
||||
const Config = @import("../config/Config.zig");
|
||||
const themepkg = @import("../config/theme.zig");
|
||||
const tui = @import("tui.zig");
|
||||
const internal_os = @import("../os/main.zig");
|
||||
const global_state = &@import("../global.zig").state;
|
||||
|
||||
const vaxis = @import("vaxis");
|
||||
@@ -180,7 +178,13 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var theme_config = try Config.default(gpa_alloc);
|
||||
defer theme_config.deinit();
|
||||
for (themes.items) |theme| {
|
||||
try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
|
||||
if (!shouldIncludeTheme(opts.color, theme_config)) {
|
||||
continue;
|
||||
}
|
||||
if (opts.path)
|
||||
try stdout.print("{s} ({t}) {s}\n", .{ theme.theme, theme.location, theme.path })
|
||||
else
|
||||
|
||||
@@ -5,7 +5,7 @@ const DiskCache = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const assert = @import("../../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const internal_os = @import("../../os/main.zig");
|
||||
const xdg = internal_os.xdg;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user