Merge remote-tracking branch 'upstream/main' into jacob/uucode
commit
90832d89b3
|
|
@ -47,7 +47,7 @@ jobs:
|
||||||
sentry-cli dif upload --project ghostty --wait dsym.zip
|
sentry-cli dif upload --project ghostty --wait dsym.zip
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -57,9 +57,9 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
determinate: true
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
with:
|
with:
|
||||||
name: ghostty
|
name: ghostty
|
||||||
|
|
@ -68,7 +68,7 @@ jobs:
|
||||||
# Setup Sparkle
|
# Setup Sparkle
|
||||||
- name: Setup Sparkle
|
- name: Setup Sparkle
|
||||||
env:
|
env:
|
||||||
SPARKLE_VERSION: 2.6.4
|
SPARKLE_VERSION: 2.7.1
|
||||||
run: |
|
run: |
|
||||||
mkdir -p .action/sparkle
|
mkdir -p .action/sparkle
|
||||||
cd .action/sparkle
|
cd .action/sparkle
|
||||||
|
|
@ -95,6 +95,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cd macos
|
cd macos
|
||||||
sudo xcode-select -s /Applications/Xcode_26.0.app
|
sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||||
|
xcodebuild -version
|
||||||
xcodebuild -target Ghostty -configuration Release
|
xcodebuild -target Ghostty -configuration Release
|
||||||
|
|
||||||
# We inject the "build number" as simply the number of commits since HEAD.
|
# We inject the "build number" as simply the number of commits since HEAD.
|
||||||
|
|
@ -201,7 +202,7 @@ jobs:
|
||||||
destination-dir: ./
|
destination-dir: ./
|
||||||
|
|
||||||
build-macos-debug:
|
build-macos-debug:
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -211,9 +212,9 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
determinate: true
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
with:
|
with:
|
||||||
name: ghostty
|
name: ghostty
|
||||||
|
|
@ -222,7 +223,7 @@ jobs:
|
||||||
# Setup Sparkle
|
# Setup Sparkle
|
||||||
- name: Setup Sparkle
|
- name: Setup Sparkle
|
||||||
env:
|
env:
|
||||||
SPARKLE_VERSION: 2.5.1
|
SPARKLE_VERSION: 2.7.1
|
||||||
run: |
|
run: |
|
||||||
mkdir -p .action/sparkle
|
mkdir -p .action/sparkle
|
||||||
cd .action/sparkle
|
cd .action/sparkle
|
||||||
|
|
@ -249,6 +250,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cd macos
|
cd macos
|
||||||
sudo xcode-select -s /Applications/Xcode_26.0.app
|
sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||||
|
xcodebuild -version
|
||||||
xcodebuild -target Ghostty -configuration Release
|
xcodebuild -target Ghostty -configuration Release
|
||||||
|
|
||||||
# We inject the "build number" as simply the number of commits since HEAD.
|
# We inject the "build number" as simply the number of commits since HEAD.
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ jobs:
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
needs: [setup]
|
needs: [setup]
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
env:
|
env:
|
||||||
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
|
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
|
||||||
|
|
@ -130,9 +130,9 @@ jobs:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
determinate: true
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
with:
|
with:
|
||||||
name: ghostty
|
name: ghostty
|
||||||
|
|
@ -141,9 +141,12 @@ jobs:
|
||||||
- name: XCode Select
|
- name: XCode Select
|
||||||
run: sudo xcode-select -s /Applications/Xcode_16.4.app
|
run: sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||||
|
|
||||||
|
- name: Xcode Version
|
||||||
|
run: xcodebuild -version
|
||||||
|
|
||||||
- name: Setup Sparkle
|
- name: Setup Sparkle
|
||||||
env:
|
env:
|
||||||
SPARKLE_VERSION: 2.6.4
|
SPARKLE_VERSION: 2.7.1
|
||||||
run: |
|
run: |
|
||||||
mkdir -p .action/sparkle
|
mkdir -p .action/sparkle
|
||||||
cd .action/sparkle
|
cd .action/sparkle
|
||||||
|
|
@ -291,7 +294,7 @@ jobs:
|
||||||
|
|
||||||
appcast:
|
appcast:
|
||||||
needs: [setup, build-macos]
|
needs: [setup, build-macos]
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
env:
|
env:
|
||||||
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
|
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
|
||||||
GHOSTTY_BUILD: ${{ needs.setup.outputs.build }}
|
GHOSTTY_BUILD: ${{ needs.setup.outputs.build }}
|
||||||
|
|
@ -308,7 +311,7 @@ jobs:
|
||||||
|
|
||||||
- name: Setup Sparkle
|
- name: Setup Sparkle
|
||||||
env:
|
env:
|
||||||
SPARKLE_VERSION: 2.6.4
|
SPARKLE_VERSION: 2.7.1
|
||||||
run: |
|
run: |
|
||||||
mkdir -p .action/sparkle
|
mkdir -p .action/sparkle
|
||||||
cd .action/sparkle
|
cd .action/sparkle
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ jobs:
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -163,10 +163,10 @@ jobs:
|
||||||
# Important so that build number generation works
|
# Important so that build number generation works
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
determinate: true
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
with:
|
with:
|
||||||
name: ghostty
|
name: ghostty
|
||||||
|
|
@ -181,7 +181,7 @@ jobs:
|
||||||
# Setup Sparkle
|
# Setup Sparkle
|
||||||
- name: Setup Sparkle
|
- name: Setup Sparkle
|
||||||
env:
|
env:
|
||||||
SPARKLE_VERSION: 2.6.4
|
SPARKLE_VERSION: 2.7.1
|
||||||
run: |
|
run: |
|
||||||
mkdir -p .action/sparkle
|
mkdir -p .action/sparkle
|
||||||
cd .action/sparkle
|
cd .action/sparkle
|
||||||
|
|
@ -374,7 +374,7 @@ jobs:
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -383,10 +383,10 @@ jobs:
|
||||||
# Important so that build number generation works
|
# Important so that build number generation works
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
determinate: true
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
with:
|
with:
|
||||||
name: ghostty
|
name: ghostty
|
||||||
|
|
@ -401,7 +401,7 @@ jobs:
|
||||||
# Setup Sparkle
|
# Setup Sparkle
|
||||||
- name: Setup Sparkle
|
- name: Setup Sparkle
|
||||||
env:
|
env:
|
||||||
SPARKLE_VERSION: 2.5.1
|
SPARKLE_VERSION: 2.7.1
|
||||||
run: |
|
run: |
|
||||||
mkdir -p .action/sparkle
|
mkdir -p .action/sparkle
|
||||||
cd .action/sparkle
|
cd .action/sparkle
|
||||||
|
|
@ -554,7 +554,7 @@ jobs:
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -563,10 +563,10 @@ jobs:
|
||||||
# Important so that build number generation works
|
# Important so that build number generation works
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
determinate: true
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
with:
|
with:
|
||||||
name: ghostty
|
name: ghostty
|
||||||
|
|
@ -581,7 +581,7 @@ jobs:
|
||||||
# Setup Sparkle
|
# Setup Sparkle
|
||||||
- name: Setup Sparkle
|
- name: Setup Sparkle
|
||||||
env:
|
env:
|
||||||
SPARKLE_VERSION: 2.5.1
|
SPARKLE_VERSION: 2.7.1
|
||||||
run: |
|
run: |
|
||||||
mkdir -p .action/sparkle
|
mkdir -p .action/sparkle
|
||||||
cd .action/sparkle
|
cd .action/sparkle
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,9 @@ jobs:
|
||||||
- build-nix
|
- build-nix
|
||||||
- build-snap
|
- build-snap
|
||||||
- build-macos
|
- build-macos
|
||||||
- build-macos-tahoe
|
|
||||||
- build-macos-matrix
|
- build-macos-matrix
|
||||||
- build-windows
|
- build-windows
|
||||||
- flatpak-check-zig-cache
|
- flatpak-check-zig-cache
|
||||||
- flatpak
|
|
||||||
- test
|
- test
|
||||||
- test-gtk
|
- test-gtk
|
||||||
- test-gtk-ng
|
- test-gtk-ng
|
||||||
|
|
@ -37,7 +35,9 @@ jobs:
|
||||||
- blueprint-compiler
|
- blueprint-compiler
|
||||||
- test-pkg-linux
|
- test-pkg-linux
|
||||||
- test-debian-13
|
- test-debian-13
|
||||||
|
- valgrind
|
||||||
- zig-fmt
|
- zig-fmt
|
||||||
|
- flatpak
|
||||||
steps:
|
steps:
|
||||||
- id: status
|
- id: status
|
||||||
name: Determine status
|
name: Determine status
|
||||||
|
|
@ -272,16 +272,16 @@ jobs:
|
||||||
ghostty-source.tar.gz
|
ghostty-source.tar.gz
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
needs: test
|
needs: test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
determinate: true
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
with:
|
with:
|
||||||
name: ghostty
|
name: ghostty
|
||||||
|
|
@ -314,7 +314,7 @@ jobs:
|
||||||
cd macos
|
cd macos
|
||||||
xcodebuild -target Ghostty-iOS "CODE_SIGNING_ALLOWED=NO"
|
xcodebuild -target Ghostty-iOS "CODE_SIGNING_ALLOWED=NO"
|
||||||
|
|
||||||
build-macos-tahoe:
|
build-macos-matrix:
|
||||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
needs: test
|
needs: test
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -333,45 +333,8 @@ jobs:
|
||||||
- name: Xcode Select
|
- name: Xcode Select
|
||||||
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||||
|
|
||||||
- name: get the Zig deps
|
- name: Xcode Version
|
||||||
id: deps
|
run: xcodebuild -version
|
||||||
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# GhosttyKit is the framework that is built from Zig for our native
|
|
||||||
# Mac app to access.
|
|
||||||
- name: Build GhosttyKit
|
|
||||||
run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false
|
|
||||||
|
|
||||||
# The native app is built with native Xcode tooling. This also does
|
|
||||||
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
|
|
||||||
# Nix breaks xcodebuild so this has to be run outside.
|
|
||||||
- name: Build Ghostty.app
|
|
||||||
run: cd macos && xcodebuild -target Ghostty
|
|
||||||
|
|
||||||
# Build the iOS target without code signing just to verify it works.
|
|
||||||
- name: Build Ghostty iOS
|
|
||||||
run: |
|
|
||||||
cd macos
|
|
||||||
xcodebuild -target Ghostty-iOS "CODE_SIGNING_ALLOWED=NO"
|
|
||||||
|
|
||||||
build-macos-matrix:
|
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
|
||||||
needs: test
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
||||||
|
|
||||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
|
||||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
|
||||||
with:
|
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
|
||||||
with:
|
|
||||||
name: ghostty
|
|
||||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
||||||
|
|
||||||
- name: Xcode Select
|
|
||||||
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
|
||||||
|
|
||||||
- name: get the Zig deps
|
- name: get the Zig deps
|
||||||
id: deps
|
id: deps
|
||||||
|
|
@ -400,6 +363,7 @@ jobs:
|
||||||
os:
|
os:
|
||||||
[namespace-profile-ghostty-snap, namespace-profile-ghostty-snap-arm64]
|
[namespace-profile-ghostty-snap, namespace-profile-ghostty-snap-arm64]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 45
|
||||||
needs: [test, build-dist]
|
needs: [test, build-dist]
|
||||||
env:
|
env:
|
||||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||||
|
|
@ -434,6 +398,7 @@ jobs:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
# this will not stop other jobs from running
|
# this will not stop other jobs from running
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
timeout-minutes: 45
|
||||||
needs: test
|
needs: test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -671,16 +636,16 @@ jobs:
|
||||||
nix develop -c zig build -Dsentry=${{ matrix.sentry }}
|
nix develop -c zig build -Dsentry=${{ matrix.sentry }}
|
||||||
|
|
||||||
test-macos:
|
test-macos:
|
||||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||||
needs: test
|
needs: test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
determinate: true
|
||||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
with:
|
with:
|
||||||
name: ghostty
|
name: ghostty
|
||||||
|
|
@ -689,6 +654,9 @@ jobs:
|
||||||
- name: Xcode Select
|
- name: Xcode Select
|
||||||
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||||
|
|
||||||
|
- name: Xcode Version
|
||||||
|
run: xcodebuild -version
|
||||||
|
|
||||||
- name: get the Zig deps
|
- name: get the Zig deps
|
||||||
id: deps
|
id: deps
|
||||||
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
|
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
|
||||||
|
|
@ -1038,3 +1006,40 @@ jobs:
|
||||||
cache-key: flatpak-builder-${{ github.sha }}
|
cache-key: flatpak-builder-${{ github.sha }}
|
||||||
arch: ${{ matrix.variant.arch }}
|
arch: ${{ matrix.variant.arch }}
|
||||||
verbose: true
|
verbose: true
|
||||||
|
|
||||||
|
valgrind:
|
||||||
|
if: github.repository == 'ghostty-org/ghostty'
|
||||||
|
runs-on: namespace-profile-ghostty-lg
|
||||||
|
timeout-minutes: 30
|
||||||
|
needs: test
|
||||||
|
env:
|
||||||
|
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||||
|
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
|
- name: Setup Cache
|
||||||
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
/nix
|
||||||
|
/zig
|
||||||
|
|
||||||
|
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||||
|
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||||
|
with:
|
||||||
|
name: ghostty
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
|
||||||
|
- name: valgrind deps
|
||||||
|
run: |
|
||||||
|
sudo apt update -y
|
||||||
|
sudo apt install -y valgrind libc6-dbg
|
||||||
|
|
||||||
|
- name: valgrind
|
||||||
|
run: |
|
||||||
|
nix develop -c zig build test-valgrind
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,40 @@ we can always convert that to an issue later.
|
||||||
> time to fixing bugs, maintaining features, and reviewing code, I do kindly
|
> time to fixing bugs, maintaining features, and reviewing code, I do kindly
|
||||||
> ask you spend a few minutes reading this document. Thank you. ❤️
|
> ask you spend a few minutes reading this document. Thank you. ❤️
|
||||||
|
|
||||||
|
## AI Assistance Notice
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
>
|
||||||
|
> If you are using **any kind of AI assistance** to contribute to Ghostty,
|
||||||
|
> it must be disclosed in the pull request.
|
||||||
|
|
||||||
|
If you are using any kind of AI assistance while contributing to Ghostty,
|
||||||
|
**this must be disclosed in the pull request**, along with the extent to
|
||||||
|
which AI assistance was used (e.g. docs only vs. code generation).
|
||||||
|
If PR responses are being generated by an AI, disclose that as well.
|
||||||
|
As a small exception, trivial tab-completion doesn't need to be disclosed,
|
||||||
|
so long as it is limited to single keywords or short phrases.
|
||||||
|
|
||||||
|
An example disclosure:
|
||||||
|
|
||||||
|
> This PR was written primarily by Claude Code.
|
||||||
|
|
||||||
|
Or a more detailed disclosure:
|
||||||
|
|
||||||
|
> I consulted ChatGPT to understand the codebase but the solution
|
||||||
|
> was fully authored manually by myself.
|
||||||
|
|
||||||
|
Failure to disclose this is first and foremost rude to the human operators
|
||||||
|
on the other end of the pull request, but it also makes it difficult to
|
||||||
|
determine how much scrutiny to apply to the contribution.
|
||||||
|
|
||||||
|
In a perfect world, AI assistance would produce equal or higher quality
|
||||||
|
work than any human. That isn't the world we live in today, and in most cases
|
||||||
|
it's generating slop. I say this despite being a fan of and using them
|
||||||
|
successfully myself (with heavy supervision)!
|
||||||
|
|
||||||
|
Please be respectful to maintainers and disclose AI assistance.
|
||||||
|
|
||||||
## Quick Guide
|
## Quick Guide
|
||||||
|
|
||||||
**I'd like to contribute!**
|
**I'd like to contribute!**
|
||||||
|
|
@ -99,6 +133,28 @@ pull request will be accepted with a high degree of certainty.
|
||||||
|
|
||||||
See the [Contributor's Guide](po/README_CONTRIBUTORS.md) for more details.
|
See the [Contributor's Guide](po/README_CONTRIBUTORS.md) for more details.
|
||||||
|
|
||||||
|
## Checking for Memory Leaks
|
||||||
|
|
||||||
|
While Zig does an amazing job of finding and preventing memory leaks,
|
||||||
|
Ghostty uses many third-party libraries that are written in C. Improper usage
|
||||||
|
of those libraries or bugs in those libraries can cause memory leaks that
|
||||||
|
Zig cannot detect by itself.
|
||||||
|
|
||||||
|
### On Linux
|
||||||
|
|
||||||
|
On Linux the recommended tool to check for memory leaks is Valgrind. The
|
||||||
|
recommended way to run Valgrind is via `zig build`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
zig build run-valgrind
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds a Ghostty executable with Valgrind support and runs Valgrind
|
||||||
|
with the proper flags to ensure we're suppressing known false positives.
|
||||||
|
|
||||||
|
You can combine the same build args with `run-valgrind` that you can with
|
||||||
|
`run`, such as specifying additional configurations after a trailing `--`.
|
||||||
|
|
||||||
## Input Stack Testing
|
## Input Stack Testing
|
||||||
|
|
||||||
The input stack is the part of the codebase that starts with a
|
The input stack is the part of the codebase that starts with a
|
||||||
|
|
|
||||||
58
build.zig
58
build.zig
|
|
@ -19,7 +19,15 @@ pub fn build(b: *std.Build) !void {
|
||||||
// All our steps which we'll hook up later. The steps are shown
|
// All our steps which we'll hook up later. The steps are shown
|
||||||
// up here just so that they are more self-documenting.
|
// up here just so that they are more self-documenting.
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Run the app");
|
||||||
const test_step = b.step("test", "Run all tests");
|
const run_valgrind_step = b.step(
|
||||||
|
"run-valgrind",
|
||||||
|
"Run the app under valgrind",
|
||||||
|
);
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
const test_valgrind_step = b.step(
|
||||||
|
"test-valgrind",
|
||||||
|
"Run tests under valgrind",
|
||||||
|
);
|
||||||
const translations_step = b.step(
|
const translations_step = b.step(
|
||||||
"update-translations",
|
"update-translations",
|
||||||
"Update translation files",
|
"Update translation files",
|
||||||
|
|
@ -77,9 +85,11 @@ pub fn build(b: *std.Build) !void {
|
||||||
|
|
||||||
// Runtime "none" is libghostty, anything else is an executable.
|
// Runtime "none" is libghostty, anything else is an executable.
|
||||||
if (config.app_runtime != .none) {
|
if (config.app_runtime != .none) {
|
||||||
exe.install();
|
if (config.emit_exe) {
|
||||||
resources.install();
|
exe.install();
|
||||||
if (i18n) |v| v.install();
|
resources.install();
|
||||||
|
if (i18n) |v| v.install();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Libghostty
|
// Libghostty
|
||||||
//
|
//
|
||||||
|
|
@ -181,6 +191,31 @@ pub fn build(b: *std.Build) !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valgrind
|
||||||
|
if (config.app_runtime != .none) {
|
||||||
|
// We need to rebuild Ghostty with a baseline CPU target.
|
||||||
|
const valgrind_exe = exe: {
|
||||||
|
var valgrind_config = config;
|
||||||
|
valgrind_config.target = valgrind_config.baselineTarget();
|
||||||
|
break :exe try buildpkg.GhosttyExe.init(
|
||||||
|
b,
|
||||||
|
&valgrind_config,
|
||||||
|
&deps,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const run_cmd = b.addSystemCommand(&.{
|
||||||
|
"valgrind",
|
||||||
|
"--leak-check=full",
|
||||||
|
"--num-callers=50",
|
||||||
|
b.fmt("--suppressions={s}", .{b.pathFromRoot("valgrind.supp")}),
|
||||||
|
"--gen-suppressions=all",
|
||||||
|
});
|
||||||
|
run_cmd.addArtifactArg(valgrind_exe.exe);
|
||||||
|
if (b.args) |args| run_cmd.addArgs(args);
|
||||||
|
run_valgrind_step.dependOn(&run_cmd.step);
|
||||||
|
}
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
{
|
{
|
||||||
const test_exe = b.addTest(.{
|
const test_exe = b.addTest(.{
|
||||||
|
|
@ -188,7 +223,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
.filters = if (test_filter) |v| &.{v} else &.{},
|
.filters = if (test_filter) |v| &.{v} else &.{},
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
.target = config.target,
|
.target = config.baselineTarget(),
|
||||||
.optimize = .Debug,
|
.optimize = .Debug,
|
||||||
.strip = false,
|
.strip = false,
|
||||||
.omit_frame_pointer = false,
|
.omit_frame_pointer = false,
|
||||||
|
|
@ -198,8 +233,21 @@ pub fn build(b: *std.Build) !void {
|
||||||
|
|
||||||
if (config.emit_test_exe) b.installArtifact(test_exe);
|
if (config.emit_test_exe) b.installArtifact(test_exe);
|
||||||
_ = try deps.add(test_exe);
|
_ = try deps.add(test_exe);
|
||||||
|
|
||||||
|
// Normal test running
|
||||||
const test_run = b.addRunArtifact(test_exe);
|
const test_run = b.addRunArtifact(test_exe);
|
||||||
test_step.dependOn(&test_run.step);
|
test_step.dependOn(&test_run.step);
|
||||||
|
|
||||||
|
// Valgrind test running
|
||||||
|
const valgrind_run = b.addSystemCommand(&.{
|
||||||
|
"valgrind",
|
||||||
|
"--leak-check=full",
|
||||||
|
"--num-callers=50",
|
||||||
|
b.fmt("--suppressions={s}", .{b.pathFromRoot("valgrind.supp")}),
|
||||||
|
"--gen-suppressions=all",
|
||||||
|
});
|
||||||
|
valgrind_run.addArtifactArg(test_exe);
|
||||||
|
test_valgrind_step.dependOn(&valgrind_run.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update-translations does what it sounds like and updates the "pot"
|
// update-translations does what it sounds like and updates the "pot"
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,8 @@
|
||||||
// Other
|
// Other
|
||||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||||
.iterm2_themes = .{
|
.iterm2_themes = .{
|
||||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz",
|
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz",
|
||||||
.hash = "N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu",
|
.hash = "N-V-__8AAAlgXwSghpDmXBXZM4Rpd80WKOXVWTrcL0ucVmls",
|
||||||
.lazy = true,
|
.lazy = true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,10 @@
|
||||||
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
|
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
|
||||||
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
|
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
|
||||||
},
|
},
|
||||||
"N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu": {
|
"N-V-__8AAAlgXwSghpDmXBXZM4Rpd80WKOXVWTrcL0ucVmls": {
|
||||||
"name": "iterm2_themes",
|
"name": "iterm2_themes",
|
||||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz",
|
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz",
|
||||||
"hash": "sha256-gl42NOZ59ok+umHCHbdBQhWCgFVpj5PAZDVGhJRpbiA="
|
"hash": "sha256-PySWF/9IAK4DZCkd5FRpiaIl6et2Qm6t8IKCTzh/Xa0="
|
||||||
},
|
},
|
||||||
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
||||||
"name": "jetbrains_mono",
|
"name": "jetbrains_mono",
|
||||||
|
|
|
||||||
|
|
@ -162,11 +162,11 @@ in
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
name = "N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu";
|
name = "N-V-__8AAAlgXwSghpDmXBXZM4Rpd80WKOXVWTrcL0ucVmls";
|
||||||
path = fetchZigArtifact {
|
path = fetchZigArtifact {
|
||||||
name = "iterm2_themes";
|
name = "iterm2_themes";
|
||||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz";
|
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz";
|
||||||
hash = "sha256-gl42NOZ59ok+umHCHbdBQhWCgFVpj5PAZDVGhJRpbiA=";
|
hash = "sha256-PySWF/9IAK4DZCkd5FRpiaIl6et2Qm6t8IKCTzh/Xa0=";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90
|
||||||
https://github.com/jacobsandlund/uucode.x/archive/ca9a9a4560307a30319d206b1ac68a7fc2f2fce9.tar.gz
|
https://github.com/jacobsandlund/uucode.x/archive/ca9a9a4560307a30319d206b1ac68a7fc2f2fce9.tar.gz
|
||||||
https://github.com/jacobsandlund/uucode/archive/658743f845f25f8f8d30f535329829660c808eaf.tar.gz
|
https://github.com/jacobsandlund/uucode/archive/658743f845f25f8f8d30f535329829660c808eaf.tar.gz
|
||||||
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst
|
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst
|
||||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz
|
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz
|
||||||
https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz
|
https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz
|
||||||
https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz
|
https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz
|
||||||
https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz
|
https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ app-id: com.mitchellh.ghostty-debug
|
||||||
runtime: org.gnome.Platform
|
runtime: org.gnome.Platform
|
||||||
runtime-version: "48"
|
runtime-version: "48"
|
||||||
sdk: org.gnome.Sdk
|
sdk: org.gnome.Sdk
|
||||||
sdk-extensions:
|
|
||||||
- org.freedesktop.Sdk.Extension.ziglang
|
|
||||||
default-branch: tip
|
default-branch: tip
|
||||||
command: ghostty
|
command: ghostty
|
||||||
rename-icon: com.mitchellh.ghostty
|
rename-icon: com.mitchellh.ghostty
|
||||||
|
|
@ -37,7 +35,7 @@ modules:
|
||||||
- name: ghostty
|
- name: ghostty
|
||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
build-options:
|
build-options:
|
||||||
append-path: /usr/lib/sdk/ziglang
|
append-path: /app/zig
|
||||||
build-commands:
|
build-commands:
|
||||||
- zig build
|
- zig build
|
||||||
-Doptimize=Debug
|
-Doptimize=Debug
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ app-id: com.mitchellh.ghostty
|
||||||
runtime: org.gnome.Platform
|
runtime: org.gnome.Platform
|
||||||
runtime-version: "48"
|
runtime-version: "48"
|
||||||
sdk: org.gnome.Sdk
|
sdk: org.gnome.Sdk
|
||||||
sdk-extensions:
|
|
||||||
- org.freedesktop.Sdk.Extension.ziglang
|
|
||||||
default-branch: tip
|
default-branch: tip
|
||||||
command: ghostty
|
command: ghostty
|
||||||
finish-args:
|
finish-args:
|
||||||
|
|
@ -36,7 +34,7 @@ modules:
|
||||||
- name: ghostty
|
- name: ghostty
|
||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
build-options:
|
build-options:
|
||||||
append-path: /usr/lib/sdk/ziglang
|
append-path: /app/zig
|
||||||
build-commands:
|
build-commands:
|
||||||
- zig build
|
- zig build
|
||||||
-Doptimize=ReleaseFast
|
-Doptimize=ReleaseFast
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,24 @@ buildsystem: simple
|
||||||
build-commands:
|
build-commands:
|
||||||
- true
|
- true
|
||||||
modules:
|
modules:
|
||||||
|
- name: zig
|
||||||
|
buildsystem: simple
|
||||||
|
cleanup:
|
||||||
|
- "*"
|
||||||
|
build-commands:
|
||||||
|
- mkdir -p /app/zig
|
||||||
|
- cp -r ./* /app/zig
|
||||||
|
- chmod a+x /app/zig/zig
|
||||||
|
sources:
|
||||||
|
- type: archive
|
||||||
|
sha256: 24aeeec8af16c381934a6cd7d95c807a8cb2cf7df9fa40d359aa884195c4716c
|
||||||
|
url: https://ziglang.org/download/0.14.1/zig-x86_64-linux-0.14.1.tar.xz
|
||||||
|
only-arches: [x86_64]
|
||||||
|
- type: archive
|
||||||
|
sha256: f7a654acc967864f7a050ddacfaa778c7504a0eca8d2b678839c21eea47c992b
|
||||||
|
url: https://ziglang.org/download/0.14.1/zig-aarch64-linux-0.14.1.tar.xz
|
||||||
|
only-arches: [aarch64]
|
||||||
|
|
||||||
- name: bzip2-redirect
|
- name: bzip2-redirect
|
||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
build-commands:
|
build-commands:
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz",
|
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz",
|
||||||
"dest": "vendor/p/N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu",
|
"dest": "vendor/p/N-V-__8AAAlgXwSghpDmXBXZM4Rpd80WKOXVWTrcL0ucVmls",
|
||||||
"sha256": "825e3634e679f6893eba61c21db7414215828055698f93c06435468494696e20"
|
"sha256": "3f249617ff4800ae0364291de4546989a225e9eb76426eadf082824f387f5dad"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/sparkle-project/Sparkle",
|
"location" : "https://github.com/sparkle-project/Sparkle",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "0ef1ee0220239b3776f433314515fd849025673f",
|
"revision" : "df074165274afaa39539c05d57b0832620775b11",
|
||||||
"version" : "2.6.4"
|
"version" : "2.7.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,9 @@ class AppDelegate: NSObject,
|
||||||
@Published private(set) var appIcon: NSImage? = nil {
|
@Published private(set) var appIcon: NSImage? = nil {
|
||||||
didSet {
|
didSet {
|
||||||
NSApplication.shared.applicationIconImage = appIcon
|
NSApplication.shared.applicationIconImage = appIcon
|
||||||
|
let appPath = Bundle.main.bundlePath
|
||||||
|
NSWorkspace.shared.setIcon(appIcon, forFile: appPath, options: [])
|
||||||
|
NSWorkspace.shared.noteFileSystemChanged(appPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,13 +258,13 @@ class AppDelegate: NSObject,
|
||||||
|
|
||||||
// Setup signal handlers
|
// Setup signal handlers
|
||||||
setupSignals()
|
setupSignals()
|
||||||
|
|
||||||
// If we launched via zig run then we need to force foreground.
|
// If we launched via zig run then we need to force foreground.
|
||||||
if Ghostty.launchSource == .zig_run {
|
if Ghostty.launchSource == .zig_run {
|
||||||
// This never gets called until we click the dock icon. This forces it
|
// This never gets called until we click the dock icon. This forces it
|
||||||
// activate immediately.
|
// activate immediately.
|
||||||
applicationDidBecomeActive(.init(name: NSApplication.didBecomeActiveNotification))
|
applicationDidBecomeActive(.init(name: NSApplication.didBecomeActiveNotification))
|
||||||
|
|
||||||
// We run in the background, this forces us to the front.
|
// We run in the background, this forces us to the front.
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
NSApp.setActivationPolicy(.regular)
|
NSApp.setActivationPolicy(.regular)
|
||||||
|
|
@ -399,11 +402,9 @@ class AppDelegate: NSObject,
|
||||||
var config = Ghostty.SurfaceConfiguration()
|
var config = Ghostty.SurfaceConfiguration()
|
||||||
|
|
||||||
if (isDirectory.boolValue) {
|
if (isDirectory.boolValue) {
|
||||||
// When opening a directory, create a new tab in the main
|
// When opening a directory, check the configuration to decide
|
||||||
// window with that as the working directory.
|
// whether to open in a new tab or new window.
|
||||||
// If no windows exist, a new one will be created.
|
|
||||||
config.workingDirectory = filename
|
config.workingDirectory = filename
|
||||||
_ = TerminalController.newTab(ghostty, withBaseConfig: config)
|
|
||||||
} else {
|
} else {
|
||||||
// When opening a file, we want to execute the file. To do this, we
|
// When opening a file, we want to execute the file. To do this, we
|
||||||
// don't override the command directly, because it won't load the
|
// don't override the command directly, because it won't load the
|
||||||
|
|
@ -415,8 +416,11 @@ class AppDelegate: NSObject,
|
||||||
// Set the parent directory to our working directory so that relative
|
// Set the parent directory to our working directory so that relative
|
||||||
// paths in scripts work.
|
// paths in scripts work.
|
||||||
config.workingDirectory = (filename as NSString).deletingLastPathComponent
|
config.workingDirectory = (filename as NSString).deletingLastPathComponent
|
||||||
|
}
|
||||||
_ = TerminalController.newWindow(ghostty, withBaseConfig: config)
|
|
||||||
|
switch ghostty.config.macosDockDropBehavior {
|
||||||
|
case .new_tab: _ = TerminalController.newTab(ghostty, withBaseConfig: config)
|
||||||
|
case .new_window: _ = TerminalController.newWindow(ghostty, withBaseConfig: config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
@ -834,6 +838,13 @@ class AppDelegate: NSObject,
|
||||||
case .xray:
|
case .xray:
|
||||||
self.appIcon = NSImage(named: "XrayImage")!
|
self.appIcon = NSImage(named: "XrayImage")!
|
||||||
|
|
||||||
|
case .custom:
|
||||||
|
if let userIcon = NSImage(contentsOfFile: config.macosCustomIcon) {
|
||||||
|
self.appIcon = userIcon
|
||||||
|
} else {
|
||||||
|
self.appIcon = nil // Revert back to official icon if invalid location
|
||||||
|
}
|
||||||
|
|
||||||
case .customStyle:
|
case .customStyle:
|
||||||
guard let ghostColor = config.macosIconGhostColor else { break }
|
guard let ghostColor = config.macosIconGhostColor else { break }
|
||||||
guard let screenColors = config.macosIconScreenColor else { break }
|
guard let screenColors = config.macosIconScreenColor else { break }
|
||||||
|
|
@ -946,18 +957,10 @@ class AppDelegate: NSObject,
|
||||||
|
|
||||||
@IBAction func newWindow(_ sender: Any?) {
|
@IBAction func newWindow(_ sender: Any?) {
|
||||||
_ = TerminalController.newWindow(ghostty)
|
_ = TerminalController.newWindow(ghostty)
|
||||||
|
|
||||||
// We also activate our app so that it becomes front. This may be
|
|
||||||
// necessary for the dock menu.
|
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func newTab(_ sender: Any?) {
|
@IBAction func newTab(_ sender: Any?) {
|
||||||
_ = TerminalController.newTab(ghostty)
|
_ = TerminalController.newTab(ghostty)
|
||||||
|
|
||||||
// We also activate our app so that it becomes front. This may be
|
|
||||||
// necessary for the dock menu.
|
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func closeAllWindows(_ sender: Any?) {
|
@IBAction func closeAllWindows(_ sender: Any?) {
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,10 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||||
}
|
}
|
||||||
|
|
||||||
c.showWindow(self)
|
c.showWindow(self)
|
||||||
|
|
||||||
|
// All new_window actions force our app to be active, so that the new
|
||||||
|
// window is focused and visible.
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup our undo
|
// Setup our undo
|
||||||
|
|
@ -332,6 +336,10 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||||
|
|
||||||
controller.showWindow(self)
|
controller.showWindow(self)
|
||||||
window.makeKeyAndOrderFront(self)
|
window.makeKeyAndOrderFront(self)
|
||||||
|
|
||||||
|
// We also activate our app so that it becomes front. This may be
|
||||||
|
// necessary for the dock menu.
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// It takes an event loop cycle until the macOS tabGroup state becomes
|
// It takes an event loop cycle until the macOS tabGroup state becomes
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ extension Ghostty {
|
||||||
let key = "window-position-x"
|
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.count)) ? v : nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var windowPositionY: Int16? {
|
var windowPositionY: Int16? {
|
||||||
guard let config = self.config else { return nil }
|
guard let config = self.config else { return nil }
|
||||||
var v: Int16 = 0
|
var v: Int16 = 0
|
||||||
|
|
@ -282,6 +282,17 @@ extension Ghostty {
|
||||||
return MacOSTitlebarProxyIcon(rawValue: str) ?? defaultValue
|
return MacOSTitlebarProxyIcon(rawValue: str) ?? defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var macosDockDropBehavior: MacDockDropBehavior {
|
||||||
|
let defaultValue = MacDockDropBehavior.new_tab
|
||||||
|
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 let ptr = v else { return defaultValue }
|
||||||
|
let str = String(cString: ptr)
|
||||||
|
return MacDockDropBehavior(rawValue: str) ?? defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
var macosWindowShadow: Bool {
|
var macosWindowShadow: Bool {
|
||||||
guard let config = self.config else { return false }
|
guard let config = self.config else { return false }
|
||||||
var v = false;
|
var v = false;
|
||||||
|
|
@ -301,6 +312,24 @@ extension Ghostty {
|
||||||
return MacOSIcon(rawValue: str) ?? defaultValue
|
return MacOSIcon(rawValue: str) ?? defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var macosCustomIcon: String {
|
||||||
|
#if os(macOS)
|
||||||
|
let homeDirURL = FileManager.default.homeDirectoryForCurrentUser
|
||||||
|
let ghosttyConfigIconPath = homeDirURL.appendingPathComponent(
|
||||||
|
".config/ghostty/Ghostty.icns",
|
||||||
|
conformingTo: .fileURL).path()
|
||||||
|
let defaultValue = ghosttyConfigIconPath
|
||||||
|
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 let ptr = v else { return defaultValue }
|
||||||
|
return String(cString: ptr)
|
||||||
|
#else
|
||||||
|
return ""
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
var macosIconFrame: MacOSIconFrame {
|
var macosIconFrame: MacOSIconFrame {
|
||||||
let defaultValue = MacOSIconFrame.aluminum
|
let defaultValue = MacOSIconFrame.aluminum
|
||||||
guard let config = self.config else { return defaultValue }
|
guard let config = self.config else { return defaultValue }
|
||||||
|
|
@ -589,6 +618,11 @@ extension Ghostty.Config {
|
||||||
static let attention = BellFeatures(rawValue: 1 << 2)
|
static let attention = BellFeatures(rawValue: 1 << 2)
|
||||||
static let title = BellFeatures(rawValue: 1 << 3)
|
static let title = BellFeatures(rawValue: 1 << 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MacDockDropBehavior: String {
|
||||||
|
case new_tab = "new-tab"
|
||||||
|
case new_window = "new-window"
|
||||||
|
}
|
||||||
|
|
||||||
enum MacHidden : String {
|
enum MacHidden : String {
|
||||||
case never
|
case never
|
||||||
|
|
|
||||||
|
|
@ -280,6 +280,7 @@ extension Ghostty {
|
||||||
case paper
|
case paper
|
||||||
case retro
|
case retro
|
||||||
case xray
|
case xray
|
||||||
|
case custom
|
||||||
case customStyle = "custom-style"
|
case customStyle = "custom-style"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1327,7 +1327,7 @@ extension Ghostty {
|
||||||
var item: NSMenuItem
|
var item: NSMenuItem
|
||||||
|
|
||||||
// If we have a selection, add copy
|
// If we have a selection, add copy
|
||||||
if self.selectedRange().length > 0 {
|
if let text = self.accessibilitySelectedText(), text.count > 0 {
|
||||||
menu.addItem(withTitle: "Copy", action: #selector(copy(_:)), keyEquivalent: "")
|
menu.addItem(withTitle: "Copy", action: #selector(copy(_:)), keyEquivalent: "")
|
||||||
}
|
}
|
||||||
menu.addItem(withTitle: "Paste", action: #selector(paste(_:)), keyEquivalent: "")
|
menu.addItem(withTitle: "Paste", action: #selector(paste(_:)), keyEquivalent: "")
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
|
|
||||||
const unit_tests = b.addTest(.{
|
const unit_tests = b.addTest(.{
|
||||||
.name = "test",
|
.name = "test",
|
||||||
.root_module = b.createModule(.{
|
.root_module = module,
|
||||||
.root_source_file = b.path("src/main.zig"),
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
unit_tests.linkLibC();
|
unit_tests.linkLibC();
|
||||||
|
|
||||||
|
|
@ -34,12 +30,6 @@ pub fn build(b: *std.Build) !void {
|
||||||
.file = wuffs_dep.path("release/c/wuffs-v0.4.c"),
|
.file = wuffs_dep.path("release/c/wuffs-v0.4.c"),
|
||||||
.flags = flags.items,
|
.flags = flags.items,
|
||||||
});
|
});
|
||||||
|
|
||||||
unit_tests.addIncludePath(wuffs_dep.path("release/c"));
|
|
||||||
unit_tests.addCSourceFile(.{
|
|
||||||
.file = wuffs_dep.path("release/c/wuffs-v0.4.c"),
|
|
||||||
.flags = flags.items,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (b.lazyDependency("pixels", .{})) |pixels_dep| {
|
if (b.lazyDependency("pixels", .{})) |pixels_dep| {
|
||||||
|
|
|
||||||
245
src/Surface.zig
245
src/Surface.zig
|
|
@ -247,6 +247,7 @@ const DerivedConfig = struct {
|
||||||
clipboard_paste_protection: bool,
|
clipboard_paste_protection: bool,
|
||||||
clipboard_paste_bracketed_safe: bool,
|
clipboard_paste_bracketed_safe: bool,
|
||||||
copy_on_select: configpkg.CopyOnSelect,
|
copy_on_select: configpkg.CopyOnSelect,
|
||||||
|
right_click_action: configpkg.RightClickAction,
|
||||||
confirm_close_surface: configpkg.ConfirmCloseSurface,
|
confirm_close_surface: configpkg.ConfirmCloseSurface,
|
||||||
cursor_click_to_move: bool,
|
cursor_click_to_move: bool,
|
||||||
desktop_notifications: bool,
|
desktop_notifications: bool,
|
||||||
|
|
@ -314,6 +315,7 @@ const DerivedConfig = struct {
|
||||||
.clipboard_paste_protection = config.@"clipboard-paste-protection",
|
.clipboard_paste_protection = config.@"clipboard-paste-protection",
|
||||||
.clipboard_paste_bracketed_safe = config.@"clipboard-paste-bracketed-safe",
|
.clipboard_paste_bracketed_safe = config.@"clipboard-paste-bracketed-safe",
|
||||||
.copy_on_select = config.@"copy-on-select",
|
.copy_on_select = config.@"copy-on-select",
|
||||||
|
.right_click_action = config.@"right-click-action",
|
||||||
.confirm_close_surface = config.@"confirm-close-surface",
|
.confirm_close_surface = config.@"confirm-close-surface",
|
||||||
.cursor_click_to_move = config.@"cursor-click-to-move",
|
.cursor_click_to_move = config.@"cursor-click-to-move",
|
||||||
.desktop_notifications = config.@"desktop-notifications",
|
.desktop_notifications = config.@"desktop-notifications",
|
||||||
|
|
@ -1529,11 +1531,6 @@ pub const Text = struct {
|
||||||
|
|
||||||
/// The viewport information about this text, if it is visible in
|
/// The viewport information about this text, if it is visible in
|
||||||
/// the viewport.
|
/// the viewport.
|
||||||
///
|
|
||||||
/// NOTE(mitchellh): This will only be non-null currently if the entirety
|
|
||||||
/// of the selection is contained within the viewport. We don't have a
|
|
||||||
/// use case currently for partial bounds but we should support this
|
|
||||||
/// eventually.
|
|
||||||
viewport: ?Viewport = null,
|
viewport: ?Viewport = null,
|
||||||
|
|
||||||
pub const Viewport = struct {
|
pub const Viewport = struct {
|
||||||
|
|
@ -1544,6 +1541,13 @@ pub const Text = struct {
|
||||||
/// The linear offset of the start of the selection and the length.
|
/// The linear offset of the start of the selection and the length.
|
||||||
/// This is "linear" in the sense that it is the offset in the
|
/// This is "linear" in the sense that it is the offset in the
|
||||||
/// flattened viewport as a single array of text.
|
/// flattened viewport as a single array of text.
|
||||||
|
///
|
||||||
|
/// Note: these values are currently wrong if there is a partially
|
||||||
|
/// visible selection in the viewport (i.e. the top-left or
|
||||||
|
/// bottom-right of the selection is outside the viewport). But the
|
||||||
|
/// apprt usecase we have right now doesn't require these to be
|
||||||
|
/// correct so... let's fix this later. The wrong values will always
|
||||||
|
/// be within the text bounds so we aren't risking an overflow.
|
||||||
offset_start: u32,
|
offset_start: u32,
|
||||||
offset_len: u32,
|
offset_len: u32,
|
||||||
};
|
};
|
||||||
|
|
@ -1585,17 +1589,57 @@ pub fn dumpTextLocked(
|
||||||
|
|
||||||
// Calculate our viewport info if we can.
|
// Calculate our viewport info if we can.
|
||||||
const vp: ?Text.Viewport = viewport: {
|
const vp: ?Text.Viewport = viewport: {
|
||||||
// If our tl or br is not in the viewport then we don't
|
// If our bottom right pin is before the viewport, then we can't
|
||||||
// have a viewport. One day we should extend this to support
|
// possibly have this text be within the viewport.
|
||||||
// partial selections that are in the viewport.
|
const vp_tl_pin = self.io.terminal.screen.pages.getTopLeft(.viewport);
|
||||||
const tl_pt = self.io.terminal.screen.pages.pointFromPin(
|
const br_pin = sel.bottomRight(&self.io.terminal.screen);
|
||||||
|
if (br_pin.before(vp_tl_pin)) break :viewport null;
|
||||||
|
|
||||||
|
// If our top-left pin is after the viewport, then we can't possibly
|
||||||
|
// have this text be within the viewport.
|
||||||
|
const vp_br_pin = self.io.terminal.screen.pages.getBottomRight(.viewport) orelse {
|
||||||
|
// I don't think this is possible but I don't want to crash on
|
||||||
|
// that assertion so let's just break out...
|
||||||
|
log.warn("viewport bottom-right pin not found, bug?", .{});
|
||||||
|
break :viewport null;
|
||||||
|
};
|
||||||
|
const tl_pin = sel.topLeft(&self.io.terminal.screen);
|
||||||
|
if (vp_br_pin.before(tl_pin)) break :viewport null;
|
||||||
|
|
||||||
|
// We established that our top-left somewhere before the viewport
|
||||||
|
// bottom-right and that our bottom-right is somewhere after
|
||||||
|
// the top-left. This means that at least some portion of our
|
||||||
|
// selection is within the viewport.
|
||||||
|
|
||||||
|
// Our top-left point. If it doesn't exist in the viewport it must
|
||||||
|
// be before and we can return (0,0).
|
||||||
|
const tl_pt: terminal.Point = self.io.terminal.screen.pages.pointFromPin(
|
||||||
.viewport,
|
.viewport,
|
||||||
sel.topLeft(&self.io.terminal.screen),
|
tl_pin,
|
||||||
) orelse break :viewport null;
|
) orelse tl: {
|
||||||
|
if (comptime std.debug.runtime_safety) {
|
||||||
|
assert(tl_pin.before(vp_tl_pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
break :tl .{ .viewport = .{} };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Our bottom-right point. If it doesn't exist in the viewport
|
||||||
|
// it must be the bottom-right of the viewport.
|
||||||
const br_pt = self.io.terminal.screen.pages.pointFromPin(
|
const br_pt = self.io.terminal.screen.pages.pointFromPin(
|
||||||
.viewport,
|
.viewport,
|
||||||
sel.bottomRight(&self.io.terminal.screen),
|
br_pin,
|
||||||
) orelse break :viewport null;
|
) orelse br: {
|
||||||
|
if (comptime std.debug.runtime_safety) {
|
||||||
|
assert(vp_br_pin.before(br_pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
break :br self.io.terminal.screen.pages.pointFromPin(
|
||||||
|
.viewport,
|
||||||
|
vp_br_pin,
|
||||||
|
).?;
|
||||||
|
};
|
||||||
|
|
||||||
const tl_coord = tl_pt.coord();
|
const tl_coord = tl_pt.coord();
|
||||||
const br_coord = br_pt.coord();
|
const br_coord = br_pt.coord();
|
||||||
|
|
||||||
|
|
@ -1666,73 +1710,6 @@ pub fn selectionString(self: *Surface, alloc: Allocator) !?[:0]const u8 {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the apprt selection metadata used by apprt's for implementing
|
|
||||||
/// things like contextual information on right click and so on.
|
|
||||||
///
|
|
||||||
/// This only returns non-null if the selection is fully contained within
|
|
||||||
/// the viewport. The use case for this function at the time of authoring
|
|
||||||
/// it is for apprt's to implement right-click contextual menus and
|
|
||||||
/// those only make sense for selections fully contained within the
|
|
||||||
/// viewport. We don't handle the case where you right click a word-wrapped
|
|
||||||
/// word at the end of the viewport yet.
|
|
||||||
pub fn selectionInfo(self: *const Surface) ?apprt.Selection {
|
|
||||||
self.renderer_state.mutex.lock();
|
|
||||||
defer self.renderer_state.mutex.unlock();
|
|
||||||
const sel = self.io.terminal.screen.selection orelse return null;
|
|
||||||
|
|
||||||
// Get the TL/BR pins for the selection and convert to viewport.
|
|
||||||
const tl = sel.topLeft(&self.io.terminal.screen);
|
|
||||||
const br = sel.bottomRight(&self.io.terminal.screen);
|
|
||||||
const tl_pt = self.io.terminal.screen.pages.pointFromPin(.viewport, tl) orelse return null;
|
|
||||||
const br_pt = self.io.terminal.screen.pages.pointFromPin(.viewport, br) orelse return null;
|
|
||||||
const tl_coord = tl_pt.coord();
|
|
||||||
const br_coord = br_pt.coord();
|
|
||||||
|
|
||||||
// Utilize viewport sizing to convert to offsets
|
|
||||||
const start = tl_coord.y * self.io.terminal.screen.pages.cols + tl_coord.x;
|
|
||||||
const end = br_coord.y * self.io.terminal.screen.pages.cols + br_coord.x;
|
|
||||||
|
|
||||||
// Our sizes are all scaled so we need to send the unscaled values back.
|
|
||||||
const content_scale = self.rt_surface.getContentScale() catch .{ .x = 1, .y = 1 };
|
|
||||||
|
|
||||||
const x: f64 = x: {
|
|
||||||
// Simple x * cell width gives the left
|
|
||||||
var x: f64 = @floatFromInt(tl_coord.x * self.size.cell.width);
|
|
||||||
|
|
||||||
// Add padding
|
|
||||||
x += @floatFromInt(self.size.padding.left);
|
|
||||||
|
|
||||||
// Scale
|
|
||||||
x /= content_scale.x;
|
|
||||||
|
|
||||||
break :x x;
|
|
||||||
};
|
|
||||||
|
|
||||||
const y: f64 = y: {
|
|
||||||
// Simple y * cell height gives the top
|
|
||||||
var y: f64 = @floatFromInt(tl_coord.y * self.size.cell.height);
|
|
||||||
|
|
||||||
// We want the text baseline
|
|
||||||
y += @floatFromInt(self.size.cell.height);
|
|
||||||
y -= @floatFromInt(self.font_metrics.cell_baseline);
|
|
||||||
|
|
||||||
// Add padding
|
|
||||||
y += @floatFromInt(self.size.padding.top);
|
|
||||||
|
|
||||||
// Scale
|
|
||||||
y /= content_scale.y;
|
|
||||||
|
|
||||||
break :y y;
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.tl_x_px = x,
|
|
||||||
.tl_y_px = y,
|
|
||||||
.offset_start = start,
|
|
||||||
.offset_len = end - start,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the pwd of the terminal, if any. This is always copied because
|
/// Returns the pwd of the terminal, if any. This is always copied because
|
||||||
/// the pwd can change at any point from termio. If we are calling from the IO
|
/// the pwd can change at any point from termio. If we are calling from the IO
|
||||||
/// thread you should just check the terminal directly.
|
/// thread you should just check the terminal directly.
|
||||||
|
|
@ -1833,6 +1810,32 @@ fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copySelectionToClipboards(
|
||||||
|
self: *Surface,
|
||||||
|
sel: terminal.Selection,
|
||||||
|
clipboards: []const apprt.Clipboard,
|
||||||
|
) void {
|
||||||
|
const buf = self.io.terminal.screen.selectionString(self.alloc, .{
|
||||||
|
.sel = sel,
|
||||||
|
.trim = self.config.clipboard_trim_trailing_spaces,
|
||||||
|
}) catch |err| {
|
||||||
|
log.err("error reading selection string err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer self.alloc.free(buf);
|
||||||
|
|
||||||
|
for (clipboards) |clipboard| self.rt_surface.setClipboardString(
|
||||||
|
buf,
|
||||||
|
clipboard,
|
||||||
|
false,
|
||||||
|
) catch |err| {
|
||||||
|
log.err(
|
||||||
|
"error setting clipboard string clipboard={} err={}",
|
||||||
|
.{ clipboard, err },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the selection contents.
|
/// Set the selection contents.
|
||||||
///
|
///
|
||||||
/// This must be called with the renderer mutex held.
|
/// This must be called with the renderer mutex held.
|
||||||
|
|
@ -1850,33 +1853,12 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
|
||||||
const sel = sel_ orelse return;
|
const sel = sel_ orelse return;
|
||||||
if (prev_) |prev| if (sel.eql(prev)) return;
|
if (prev_) |prev| if (sel.eql(prev)) return;
|
||||||
|
|
||||||
const buf = self.io.terminal.screen.selectionString(self.alloc, .{
|
|
||||||
.sel = sel,
|
|
||||||
.trim = self.config.clipboard_trim_trailing_spaces,
|
|
||||||
}) catch |err| {
|
|
||||||
log.err("error reading selection string err={}", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(buf);
|
|
||||||
|
|
||||||
// Set the clipboard. This is not super DRY but it is clear what
|
|
||||||
// we're doing for each setting without being clever.
|
|
||||||
switch (self.config.copy_on_select) {
|
switch (self.config.copy_on_select) {
|
||||||
.false => unreachable, // handled above with an early exit
|
.false => unreachable, // handled above with an early exit
|
||||||
|
|
||||||
// Both standard and selection clipboards are set.
|
// Both standard and selection clipboards are set.
|
||||||
.clipboard => {
|
.clipboard => {
|
||||||
const clipboards: []const apprt.Clipboard = &.{ .standard, .selection };
|
self.copySelectionToClipboards(sel, &.{ .standard, .selection });
|
||||||
for (clipboards) |clipboard| self.rt_surface.setClipboardString(
|
|
||||||
buf,
|
|
||||||
clipboard,
|
|
||||||
false,
|
|
||||||
) catch |err| {
|
|
||||||
log.err(
|
|
||||||
"error setting clipboard string clipboard={} err={}",
|
|
||||||
.{ clipboard, err },
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// The selection clipboard is set if supported, otherwise the standard.
|
// The selection clipboard is set if supported, otherwise the standard.
|
||||||
|
|
@ -1885,17 +1867,7 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
|
||||||
.selection
|
.selection
|
||||||
else
|
else
|
||||||
.standard;
|
.standard;
|
||||||
|
self.copySelectionToClipboards(sel, &.{clipboard});
|
||||||
self.rt_surface.setClipboardString(
|
|
||||||
buf,
|
|
||||||
clipboard,
|
|
||||||
false,
|
|
||||||
) catch |err| {
|
|
||||||
log.err(
|
|
||||||
"error setting clipboard string clipboard={} err={}",
|
|
||||||
.{ clipboard, err },
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3582,18 +3554,49 @@ pub fn mouseButtonCallback(
|
||||||
break :pin pin;
|
break :pin pin;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we already have a selection and the selection contains
|
switch (self.config.right_click_action) {
|
||||||
// where we clicked then we don't want to modify the selection.
|
.ignore => {
|
||||||
if (self.io.terminal.screen.selection) |prev_sel| {
|
// Return early to skip clearing the selection.
|
||||||
if (prev_sel.contains(screen, pin)) break :sel;
|
try self.queueRender();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.copy => {
|
||||||
|
if (self.io.terminal.screen.selection) |sel| {
|
||||||
|
self.copySelectionToClipboards(sel, &.{.standard});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.@"copy-or-paste" => {
|
||||||
|
if (self.io.terminal.screen.selection) |sel| {
|
||||||
|
self.copySelectionToClipboards(sel, &.{.standard});
|
||||||
|
} else {
|
||||||
|
try self.startClipboardRequest(.standard, .paste);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.paste => {
|
||||||
|
try self.startClipboardRequest(.standard, .paste);
|
||||||
|
},
|
||||||
|
.@"context-menu" => {
|
||||||
|
// If we already have a selection and the selection contains
|
||||||
|
// where we clicked then we don't want to modify the selection.
|
||||||
|
if (self.io.terminal.screen.selection) |prev_sel| {
|
||||||
|
if (prev_sel.contains(screen, pin)) break :sel;
|
||||||
|
|
||||||
// The selection doesn't contain our pin, so we create a new
|
// The selection doesn't contain our pin, so we create a new
|
||||||
// word selection where we clicked.
|
// word selection where we clicked.
|
||||||
|
}
|
||||||
|
|
||||||
|
const sel = screen.selectWord(pin) orelse break :sel;
|
||||||
|
try self.setSelection(sel);
|
||||||
|
try self.queueRender();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const sel = screen.selectWord(pin) orelse break :sel;
|
try self.setSelection(null);
|
||||||
try self.setSelection(sel);
|
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
|
|
||||||
|
// Consume the event such that the context menu is not displayed.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! This files contains all the GObject classes for the GTK apprt
|
//! This files contains all the GObject classes for the GTK apprt
|
||||||
//! along with helpers to work with them.
|
//! along with helpers to work with them.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
const glib = @import("glib");
|
const glib = @import("glib");
|
||||||
const gobject = @import("gobject");
|
const gobject = @import("gobject");
|
||||||
const gtk = @import("gtk");
|
const gtk = @import("gtk");
|
||||||
|
|
@ -53,6 +54,111 @@ pub fn Common(
|
||||||
}
|
}
|
||||||
}).private else {};
|
}).private else {};
|
||||||
|
|
||||||
|
/// Get the class for the object.
|
||||||
|
///
|
||||||
|
/// This _seems_ ugly and unsafe but this is how GObject
|
||||||
|
/// works under the hood. From the [GObject Type System
|
||||||
|
/// Concepts](https://docs.gtk.org/gobject/concepts.html) documentation:
|
||||||
|
///
|
||||||
|
/// Every object must define two structures: its class structure
|
||||||
|
/// and its instance structure. All class structures must contain
|
||||||
|
/// as first member a GTypeClass structure. All instance structures
|
||||||
|
/// must contain as first member a GTypeInstance structure.
|
||||||
|
/// …
|
||||||
|
/// These constraints allow the type system to make sure that
|
||||||
|
/// every object instance (identified by a pointer to the object’s
|
||||||
|
/// instance structure) contains in its first bytes a pointer to the
|
||||||
|
/// object’s class structure.
|
||||||
|
/// …
|
||||||
|
/// The C standard mandates that the first field of a C structure is
|
||||||
|
/// stored starting in the first byte of the buffer used to hold the
|
||||||
|
/// structure’s fields in memory. This means that the first field of
|
||||||
|
/// an instance of an object B is A’s first field which in turn is
|
||||||
|
/// GTypeInstance‘s first field which in turn is g_class, a pointer
|
||||||
|
/// to B’s class structure.
|
||||||
|
///
|
||||||
|
/// This means that to access the class structure for an object you cast it
|
||||||
|
/// to `*gobject.TypeInstance` and then access the `f_g_class` field.
|
||||||
|
///
|
||||||
|
/// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L555-571
|
||||||
|
/// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L2673
|
||||||
|
///
|
||||||
|
pub fn getClass(self: *Self) ?*Self.Class {
|
||||||
|
const type_instance: *gobject.TypeInstance = @ptrCast(self);
|
||||||
|
return @ptrCast(type_instance.f_g_class orelse return null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define a virtual method. The `Self.Class` type must have a field
|
||||||
|
/// named `name` which is a function pointer in the following form:
|
||||||
|
///
|
||||||
|
/// ?*const fn (*Self) callconv(.c) void
|
||||||
|
///
|
||||||
|
/// The virtual method may take additional parameters and specify
|
||||||
|
/// a non-void return type. The parameters and return type must be
|
||||||
|
/// valid for the C calling convention.
|
||||||
|
pub fn defineVirtualMethod(
|
||||||
|
comptime name: [:0]const u8,
|
||||||
|
) type {
|
||||||
|
return struct {
|
||||||
|
pub fn call(
|
||||||
|
class: anytype,
|
||||||
|
object: *ClassInstance(@TypeOf(class)),
|
||||||
|
params: anytype,
|
||||||
|
) (fn_info.return_type orelse void) {
|
||||||
|
const func = @field(
|
||||||
|
gobject.ext.as(Self.Class, class),
|
||||||
|
name,
|
||||||
|
).?;
|
||||||
|
@call(.auto, func, .{
|
||||||
|
gobject.ext.as(Self, object),
|
||||||
|
} ++ params);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn implement(
|
||||||
|
class: anytype,
|
||||||
|
implementation: *const ImplementFunc(@TypeOf(class)),
|
||||||
|
) void {
|
||||||
|
@field(gobject.ext.as(
|
||||||
|
Self.Class,
|
||||||
|
class,
|
||||||
|
), name) = @ptrCast(implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type info of the virtual method.
|
||||||
|
const fn_info = fn_info: {
|
||||||
|
// This is broken down like this so its slightly more
|
||||||
|
// readable. We expect a field named "name" on the Class
|
||||||
|
// with the rough type of `?*const fn` and we need the
|
||||||
|
// function info.
|
||||||
|
const Field = @FieldType(Self.Class, name);
|
||||||
|
const opt = @typeInfo(Field).optional;
|
||||||
|
const ptr = @typeInfo(opt.child).pointer;
|
||||||
|
break :fn_info @typeInfo(ptr.child).@"fn";
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The instance type for a class.
|
||||||
|
fn ClassInstance(comptime T: type) type {
|
||||||
|
return @typeInfo(T).pointer.child.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The function type for implementations. This is the same type
|
||||||
|
/// as the virtual method but the self parameter points to the
|
||||||
|
/// target instead of the original class.
|
||||||
|
fn ImplementFunc(comptime T: type) type {
|
||||||
|
var params: [fn_info.params.len]std.builtin.Type.Fn.Param = undefined;
|
||||||
|
@memcpy(¶ms, fn_info.params);
|
||||||
|
params[0].type = *ClassInstance(T);
|
||||||
|
return @Type(.{ .@"fn" = .{
|
||||||
|
.calling_convention = fn_info.calling_convention,
|
||||||
|
.is_generic = fn_info.is_generic,
|
||||||
|
.is_var_args = fn_info.is_var_args,
|
||||||
|
.return_type = fn_info.return_type,
|
||||||
|
.params = ¶ms,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// A helper that creates a property that reads and writes a
|
/// A helper that creates a property that reads and writes a
|
||||||
/// private field with only shallow copies. This is good for primitives
|
/// private field with only shallow copies. This is good for primitives
|
||||||
/// such as bools, numbers, etc.
|
/// such as bools, numbers, etc.
|
||||||
|
|
|
||||||
|
|
@ -2216,7 +2216,7 @@ const Action = struct {
|
||||||
Window,
|
Window,
|
||||||
surface.as(gtk.Widget),
|
surface.as(gtk.Widget),
|
||||||
) orelse {
|
) orelse {
|
||||||
log.warn("surface is not in a window, ignoring new_tab", .{});
|
log.warn("surface is not in a window, ignoring toggle_window_decorations", .{});
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,36 +35,18 @@ pub const ImguiWidget = extern struct {
|
||||||
|
|
||||||
pub const properties = struct {};
|
pub const properties = struct {};
|
||||||
|
|
||||||
pub const signals = struct {
|
pub const signals = struct {};
|
||||||
/// Emitted when the child widget should render. During the callback,
|
|
||||||
/// the Imgui context is valid.
|
|
||||||
pub const render = struct {
|
|
||||||
pub const name = "render";
|
|
||||||
pub const connect = impl.connect;
|
|
||||||
const impl = gobject.ext.defineSignal(
|
|
||||||
name,
|
|
||||||
Self,
|
|
||||||
&.{},
|
|
||||||
void,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Emitted when first realized to allow the embedded ImGui application
|
pub const virtual_methods = struct {
|
||||||
/// to initialize itself. When this is called, the ImGui context
|
/// This virtual method will be called to allow the Dear ImGui
|
||||||
/// is properly set.
|
/// application to do one-time setup of the context. The correct context
|
||||||
///
|
/// will be current when the virtual method is called.
|
||||||
/// This might be called multiple times, but each time it is
|
pub const setup = C.defineVirtualMethod("setup");
|
||||||
/// called a new Imgui context will be created.
|
|
||||||
pub const setup = struct {
|
/// This virtual method will be called at each frame to allow the Dear
|
||||||
pub const name = "setup";
|
/// ImGui application to draw the application. The correct context will
|
||||||
pub const connect = impl.connect;
|
/// be current when the virtual method is called.
|
||||||
const impl = gobject.ext.defineSignal(
|
pub const render = C.defineVirtualMethod("render");
|
||||||
name,
|
|
||||||
Self,
|
|
||||||
&.{},
|
|
||||||
void,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Private = struct {
|
const Private = struct {
|
||||||
|
|
@ -113,6 +95,25 @@ pub const ImguiWidget = extern struct {
|
||||||
priv.gl_area.queueRender();
|
priv.gl_area.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Public wrappers for virtual methods
|
||||||
|
|
||||||
|
/// This virtual method will be called to allow the Dear ImGui application
|
||||||
|
/// to do one-time setup of the context. The correct context will be current
|
||||||
|
/// when the virtual method is called.
|
||||||
|
pub fn setup(self: *Self) callconv(.c) void {
|
||||||
|
const class = self.getClass() orelse return;
|
||||||
|
virtual_methods.setup.call(class, self, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This virtual method will be called at each frame to allow the Dear ImGui
|
||||||
|
/// application to draw the application. The correct context will be current
|
||||||
|
/// when the virtual method is called.
|
||||||
|
pub fn render(self: *Self) callconv(.c) void {
|
||||||
|
const class = self.getClass() orelse return;
|
||||||
|
virtual_methods.render.call(class, self, .{});
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Private Methods
|
// Private Methods
|
||||||
|
|
||||||
|
|
@ -232,13 +233,8 @@ pub const ImguiWidget = extern struct {
|
||||||
// initialize the ImgUI OpenGL backend for our context.
|
// initialize the ImgUI OpenGL backend for our context.
|
||||||
_ = cimgui.ImGui_ImplOpenGL3_Init(null);
|
_ = cimgui.ImGui_ImplOpenGL3_Init(null);
|
||||||
|
|
||||||
// Setup our app
|
// Call the virtual method to setup the UI.
|
||||||
signals.setup.impl.emit(
|
self.setup();
|
||||||
self,
|
|
||||||
null,
|
|
||||||
.{},
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a request to unrealize the GLArea
|
/// Handle a request to unrealize the GLArea
|
||||||
|
|
@ -279,13 +275,8 @@ pub const ImguiWidget = extern struct {
|
||||||
self.newFrame();
|
self.newFrame();
|
||||||
cimgui.c.igNewFrame();
|
cimgui.c.igNewFrame();
|
||||||
|
|
||||||
// Use the callback to draw the UI.
|
// Call the virtual method to draw the UI.
|
||||||
signals.render.impl.emit(
|
self.render();
|
||||||
self,
|
|
||||||
null,
|
|
||||||
.{},
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
cimgui.c.igRender();
|
cimgui.c.igRender();
|
||||||
|
|
@ -422,15 +413,34 @@ pub const ImguiWidget = extern struct {
|
||||||
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, bytes);
|
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Default virtual method handlers
|
||||||
|
|
||||||
|
/// Default setup function. Does nothing but log a warning.
|
||||||
|
fn defaultSetup(_: *Self) callconv(.c) void {
|
||||||
|
log.warn("default Dear ImGui setup called, this is a bug.", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default render function. Does nothing but log a warning.
|
||||||
|
fn defaultRender(_: *Self) callconv(.c) void {
|
||||||
|
log.warn("default Dear ImGui render called, this is a bug.", .{});
|
||||||
|
}
|
||||||
|
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
pub const refSink = C.refSink;
|
pub const refSink = C.refSink;
|
||||||
pub const unref = C.unref;
|
pub const unref = C.unref;
|
||||||
|
pub const getClass = C.getClass;
|
||||||
const private = C.private;
|
const private = C.private;
|
||||||
|
|
||||||
pub const Class = extern struct {
|
pub const Class = extern struct {
|
||||||
parent_class: Parent.Class,
|
parent_class: Parent.Class,
|
||||||
|
|
||||||
|
/// Function pointers for virtual methods.
|
||||||
|
setup: ?*const fn (*Self) callconv(.c) void,
|
||||||
|
render: ?*const fn (*Self) callconv(.c) void,
|
||||||
|
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
|
|
@ -444,6 +454,10 @@ pub const ImguiWidget = extern struct {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Initialize our virtual methods with default functions.
|
||||||
|
class.setup = defaultSetup;
|
||||||
|
class.render = defaultRender;
|
||||||
|
|
||||||
// Bindings
|
// Bindings
|
||||||
class.bindTemplateChildPrivate("gl_area", .{});
|
class.bindTemplateChildPrivate("gl_area", .{});
|
||||||
class.bindTemplateChildPrivate("im_context", .{});
|
class.bindTemplateChildPrivate("im_context", .{});
|
||||||
|
|
@ -464,8 +478,6 @@ pub const ImguiWidget = extern struct {
|
||||||
class.bindTemplateCallback("im_commit", &imCommit);
|
class.bindTemplateCallback("im_commit", &imCommit);
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
signals.render.impl.register(.{});
|
|
||||||
signals.setup.impl.register(.{});
|
|
||||||
|
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const log = std.log.scoped(.gtk_ghostty_inspector_widget);
|
||||||
pub const InspectorWidget = extern struct {
|
pub const InspectorWidget = extern struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
parent_instance: Parent,
|
parent_instance: Parent,
|
||||||
pub const Parent = adw.Bin;
|
pub const Parent = ImguiWidget;
|
||||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||||
.name = "GhosttyInspectorWidget",
|
.name = "GhosttyInspectorWidget",
|
||||||
.instanceInit = &init,
|
.instanceInit = &init,
|
||||||
|
|
@ -50,9 +50,6 @@ pub const InspectorWidget = extern struct {
|
||||||
/// We attach a weak notify to the object.
|
/// We attach a weak notify to the object.
|
||||||
surface: ?*Surface = null,
|
surface: ?*Surface = null,
|
||||||
|
|
||||||
/// The embedded Dear ImGui widget.
|
|
||||||
imgui_widget: *ImguiWidget,
|
|
||||||
|
|
||||||
pub var offset: c_int = 0;
|
pub var offset: c_int = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -78,13 +75,30 @@ pub const InspectorWidget = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called to do initial setup of the UI.
|
||||||
|
fn imguiSetup(
|
||||||
|
_: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
Inspector.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called for every frame to draw the UI.
|
||||||
|
fn imguiRender(
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
const surface = priv.surface orelse return;
|
||||||
|
const core_surface = surface.core() orelse return;
|
||||||
|
const inspector = core_surface.inspector orelse return;
|
||||||
|
inspector.render();
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Public methods
|
// Public methods
|
||||||
|
|
||||||
/// Queue a render of the Dear ImGui widget.
|
/// Queue a render of the Dear ImGui widget.
|
||||||
pub fn queueRender(self: *Self) void {
|
pub fn queueRender(self: *Self) void {
|
||||||
const priv = self.private();
|
self.as(ImguiWidget).queueRender();
|
||||||
priv.imgui_widget.queueRender();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
|
|
@ -189,24 +203,6 @@ pub const InspectorWidget = extern struct {
|
||||||
// for completeness sake we should clean this up.
|
// for completeness sake we should clean this up.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn imguiRender(
|
|
||||||
_: *ImguiWidget,
|
|
||||||
self: *Self,
|
|
||||||
) callconv(.c) void {
|
|
||||||
const priv = self.private();
|
|
||||||
const surface = priv.surface orelse return;
|
|
||||||
const core_surface = surface.core() orelse return;
|
|
||||||
const inspector = core_surface.inspector orelse return;
|
|
||||||
inspector.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn imguiSetup(
|
|
||||||
_: *ImguiWidget,
|
|
||||||
_: *Self,
|
|
||||||
) callconv(.c) void {
|
|
||||||
Inspector.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
|
|
@ -230,13 +226,6 @@ pub const InspectorWidget = extern struct {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Bindings
|
|
||||||
class.bindTemplateChildPrivate("imgui_widget", .{});
|
|
||||||
|
|
||||||
// Template callbacks
|
|
||||||
class.bindTemplateCallback("imgui_render", &imguiRender);
|
|
||||||
class.bindTemplateCallback("imgui_setup", &imguiSetup);
|
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
gobject.ext.registerProperties(class, &.{
|
gobject.ext.registerProperties(class, &.{
|
||||||
properties.surface.impl,
|
properties.surface.impl,
|
||||||
|
|
@ -245,6 +234,8 @@ pub const InspectorWidget = extern struct {
|
||||||
// Signals
|
// Signals
|
||||||
|
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
|
ImguiWidget.virtual_methods.setup.implement(class, imguiSetup);
|
||||||
|
ImguiWidget.virtual_methods.render.implement(class, imguiRender);
|
||||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -554,13 +554,21 @@ pub const Surface = extern struct {
|
||||||
config_: ?*Config,
|
config_: ?*Config,
|
||||||
bell_ringing_: c_int,
|
bell_ringing_: c_int,
|
||||||
) callconv(.c) c_int {
|
) callconv(.c) c_int {
|
||||||
|
const bell_ringing = bell_ringing_ != 0;
|
||||||
|
|
||||||
|
// If the bell isn't ringing exit early because when the surface is
|
||||||
|
// first created there's a race between this code being run and the
|
||||||
|
// config being set on the surface. That way we don't overwhelm people
|
||||||
|
// with the warning that we issue if the config isn't set and overwhelm
|
||||||
|
// ourselves with large numbers of bug reports.
|
||||||
|
if (!bell_ringing) return @intFromBool(false);
|
||||||
|
|
||||||
const config = if (config_) |v| v.get() else {
|
const config = if (config_) |v| v.get() else {
|
||||||
log.warn("config unavailable for computing whether border should be shown , likely bug", .{});
|
log.warn("config unavailable for computing whether border should be shown, likely bug", .{});
|
||||||
return @intFromBool(false);
|
return @intFromBool(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const bell_ringing = bell_ringing_ != 0;
|
return @intFromBool(config.@"bell-features".border);
|
||||||
return @intFromBool(config.@"bell-features".border and bell_ringing);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggleFullscreen(self: *Self) void {
|
pub fn toggleFullscreen(self: *Self) void {
|
||||||
|
|
@ -830,7 +838,7 @@ pub const Surface = extern struct {
|
||||||
// such as single quote on a US international keyboard layout.
|
// such as single quote on a US international keyboard layout.
|
||||||
if (priv.im_composing) return true;
|
if (priv.im_composing) return true;
|
||||||
|
|
||||||
// If we were composing and now we're not it means that we committed
|
// If we were composing and now we're not, it means that we committed
|
||||||
// the text. We also don't want to encode a key event for this.
|
// the text. We also don't want to encode a key event for this.
|
||||||
// Example: enable Japanese input method, press "konn" and then
|
// Example: enable Japanese input method, press "konn" and then
|
||||||
// press enter. The final enter should not be encoded and "konn"
|
// press enter. The final enter should not be encoded and "konn"
|
||||||
|
|
@ -870,9 +878,24 @@ pub const Surface = extern struct {
|
||||||
|
|
||||||
// We want to get the physical unmapped key to process physical keybinds.
|
// We want to get the physical unmapped key to process physical keybinds.
|
||||||
// (These are keybinds explicitly marked as requesting physical mapping).
|
// (These are keybinds explicitly marked as requesting physical mapping).
|
||||||
const physical_key = keycode: for (input.keycodes.entries) |entry| {
|
const physical_key = keycode: {
|
||||||
if (entry.native == keycode) break :keycode entry.key;
|
const w3c_key: input.Key = w3c: for (input.keycodes.entries) |entry| {
|
||||||
} else .unidentified;
|
if (entry.native == keycode) break :w3c entry.key;
|
||||||
|
} else .unidentified;
|
||||||
|
|
||||||
|
// If the key should be remappable, then consult the pre-remapped
|
||||||
|
// XKB keyval/keysym to get the (possibly) remapped key.
|
||||||
|
//
|
||||||
|
// See the docs for `shouldBeRemappable` for why we even have to
|
||||||
|
// do this in the first place.
|
||||||
|
if (w3c_key.shouldBeRemappable()) {
|
||||||
|
if (gtk_key.keyFromKeyval(keyval)) |remapped|
|
||||||
|
break :keycode remapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the original physical key
|
||||||
|
break :keycode w3c_key;
|
||||||
|
};
|
||||||
|
|
||||||
// Get our modifier for the event
|
// Get our modifier for the event
|
||||||
const mods: input.Mods = gtk_key.eventMods(
|
const mods: input.Mods = gtk_key.eventMods(
|
||||||
|
|
|
||||||
|
|
@ -301,24 +301,6 @@ pub const Window = extern struct {
|
||||||
// Initialize our actions
|
// Initialize our actions
|
||||||
self.initActionMap();
|
self.initActionMap();
|
||||||
|
|
||||||
// We need to setup resize notifications on our surface
|
|
||||||
if (self.as(gtk.Native).getSurface()) |gdk_surface| {
|
|
||||||
_ = gobject.Object.signals.notify.connect(
|
|
||||||
gdk_surface,
|
|
||||||
*Self,
|
|
||||||
propGdkSurfaceWidth,
|
|
||||||
self,
|
|
||||||
.{ .detail = "width" },
|
|
||||||
);
|
|
||||||
_ = gobject.Object.signals.notify.connect(
|
|
||||||
gdk_surface,
|
|
||||||
*Self,
|
|
||||||
propGdkSurfaceHeight,
|
|
||||||
self,
|
|
||||||
.{ .detail = "height" },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start states based on config.
|
// Start states based on config.
|
||||||
if (priv.config) |config_obj| {
|
if (priv.config) |config_obj| {
|
||||||
const config = config_obj.get();
|
const config = config_obj.get();
|
||||||
|
|
@ -810,9 +792,18 @@ pub const Window = extern struct {
|
||||||
|
|
||||||
/// Toggle the window decorations for this window.
|
/// Toggle the window decorations for this window.
|
||||||
pub fn toggleWindowDecorations(self: *Self) void {
|
pub fn toggleWindowDecorations(self: *Self) void {
|
||||||
self.setWindowDecoration(switch (self.getWindowDecoration()) {
|
const priv = self.private();
|
||||||
// Null will force using the central config
|
|
||||||
.none => null,
|
if (priv.window_decoration) |_| {
|
||||||
|
// Unset any previously set window decoration settings
|
||||||
|
self.setWindowDecoration(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = if (priv.config) |v| v.get() else return;
|
||||||
|
self.setWindowDecoration(switch (config.@"window-decoration") {
|
||||||
|
// Use auto when the decoration is initially none
|
||||||
|
.none => .auto,
|
||||||
|
|
||||||
// Anything non-none to none
|
// Anything non-none to none
|
||||||
.auto, .client, .server => .none,
|
.auto, .client, .server => .none,
|
||||||
|
|
@ -1154,6 +1145,25 @@ pub const Window = extern struct {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to setup resize notifications on our surface,
|
||||||
|
// which is only available after the window had been realized.
|
||||||
|
if (self.as(gtk.Native).getSurface()) |gdk_surface| {
|
||||||
|
_ = gobject.Object.signals.notify.connect(
|
||||||
|
gdk_surface,
|
||||||
|
*Self,
|
||||||
|
propGdkSurfaceWidth,
|
||||||
|
self,
|
||||||
|
.{ .detail = "width" },
|
||||||
|
);
|
||||||
|
_ = gobject.Object.signals.notify.connect(
|
||||||
|
gdk_surface,
|
||||||
|
*Self,
|
||||||
|
propGdkSurfaceHeight,
|
||||||
|
self,
|
||||||
|
.{ .detail = "height" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// When we are realized we always setup our appearance since this
|
// When we are realized we always setup our appearance since this
|
||||||
// calls some winproto functions.
|
// calls some winproto functions.
|
||||||
self.syncAppearance();
|
self.syncAppearance();
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,10 @@ pub fn addAsGroup(comptime T: type, self: *T, comptime name: [:0]const u8, actio
|
||||||
|
|
||||||
test "adding actions to an object" {
|
test "adding actions to an object" {
|
||||||
// This test requires a connection to an active display environment.
|
// This test requires a connection to an active display environment.
|
||||||
if (gtk.initCheck() == 0) return;
|
if (gtk.initCheck() == 0) return error.SkipZigTest;
|
||||||
|
|
||||||
|
_ = glib.MainContext.acquire(null);
|
||||||
|
defer glib.MainContext.release(null);
|
||||||
|
|
||||||
const callbacks = struct {
|
const callbacks = struct {
|
||||||
fn callback(_: *gio.SimpleAction, variant_: ?*glib.Variant, self: *gtk.Box) callconv(.c) void {
|
fn callback(_: *gio.SimpleAction, variant_: ?*glib.Variant, self: *gtk.Box) callconv(.c) void {
|
||||||
|
|
@ -155,4 +158,6 @@ test "adding actions to an object" {
|
||||||
|
|
||||||
const actual = value.getInt();
|
const actual = value.getInt();
|
||||||
try testing.expectEqual(expected, actual);
|
try testing.expectEqual(expected, actual);
|
||||||
|
|
||||||
|
while (glib.MainContext.iteration(null, 0) != 0) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
//! DBus helper for IPC
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const gio = @import("gio");
|
||||||
|
const glib = @import("glib");
|
||||||
|
|
||||||
|
const apprt = @import("../../../apprt.zig");
|
||||||
|
const ApprtApp = @import("../App.zig");
|
||||||
|
|
||||||
|
/// The target for this IPC.
|
||||||
|
target: apprt.ipc.Target,
|
||||||
|
|
||||||
|
/// Connection to the DBus session bus.
|
||||||
|
dbus: *gio.DBusConnection,
|
||||||
|
|
||||||
|
/// The bus name of the Ghostty instance that we are calling.
|
||||||
|
bus_name: [:0]const u8,
|
||||||
|
|
||||||
|
/// The object path of the Ghostty instance that we are calling.
|
||||||
|
object_path: [:0]const u8,
|
||||||
|
|
||||||
|
/// Used to build the DBus payload.
|
||||||
|
payload_builder: *glib.VariantBuilder,
|
||||||
|
|
||||||
|
/// Used to build the parameters for the IPC.
|
||||||
|
parameters_builder: *glib.VariantBuilder,
|
||||||
|
|
||||||
|
/// Initialize the helper.
|
||||||
|
pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!Self {
|
||||||
|
|
||||||
|
// Get the appropriate bus name and object path for contacting the
|
||||||
|
// Ghostty instance we're interested in.
|
||||||
|
const bus_name: [:0]const u8, const object_path: [:0]const u8 = switch (target) {
|
||||||
|
.class => |class| result: {
|
||||||
|
// Force the usage of the class specified on the CLI to determine the
|
||||||
|
// bus name and object path.
|
||||||
|
const object_path = try std.fmt.allocPrintZ(alloc, "/{s}", .{class});
|
||||||
|
|
||||||
|
std.mem.replaceScalar(u8, object_path, '.', '/');
|
||||||
|
std.mem.replaceScalar(u8, object_path, '-', '_');
|
||||||
|
|
||||||
|
break :result .{ class, object_path };
|
||||||
|
},
|
||||||
|
.detect => .{ ApprtApp.application_id, ApprtApp.object_path },
|
||||||
|
};
|
||||||
|
errdefer {
|
||||||
|
switch (target) {
|
||||||
|
.class => alloc.free(object_path),
|
||||||
|
.detect => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gio.Application.idIsValid(bus_name.ptr) == 0) {
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
try stderr.print("D-Bus bus name is not valid: {s}\n", .{bus_name});
|
||||||
|
return error.IPCFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glib.Variant.isObjectPath(object_path.ptr) == 0) {
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
try stderr.print("D-Bus object path is not valid: {s}\n", .{object_path});
|
||||||
|
return error.IPCFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a connection to the DBus session bus.
|
||||||
|
const dbus = dbus: {
|
||||||
|
var err_: ?*glib.Error = null;
|
||||||
|
defer if (err_) |err| err.free();
|
||||||
|
|
||||||
|
const dbus_ = gio.busGetSync(.session, null, &err_);
|
||||||
|
if (err_) |err| {
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
try stderr.print(
|
||||||
|
"Unable to establish connection to D-Bus session bus: {s}\n",
|
||||||
|
.{err.f_message orelse "(unknown)"},
|
||||||
|
);
|
||||||
|
return error.IPCFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :dbus dbus_ orelse {
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
try stderr.print("gio.busGetSync returned null\n", .{});
|
||||||
|
return error.IPCFailed;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up the payload builder.
|
||||||
|
const payload_variant_type = glib.VariantType.new("(sava{sv})");
|
||||||
|
defer glib.free(payload_variant_type);
|
||||||
|
|
||||||
|
const payload_builder = glib.VariantBuilder.new(payload_variant_type);
|
||||||
|
|
||||||
|
// Add the action name to the payload.
|
||||||
|
{
|
||||||
|
const s_variant_type = glib.VariantType.new("s");
|
||||||
|
defer s_variant_type.free();
|
||||||
|
|
||||||
|
const bytes = glib.Bytes.new(action.ptr, action.len + 1);
|
||||||
|
defer bytes.unref();
|
||||||
|
const value = glib.Variant.newFromBytes(s_variant_type, bytes, @intFromBool(true));
|
||||||
|
|
||||||
|
payload_builder.addValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the parameter builder.
|
||||||
|
const parameters_variant_type = glib.VariantType.new("av");
|
||||||
|
defer parameters_variant_type.free();
|
||||||
|
|
||||||
|
const parameters_builder = glib.VariantBuilder.new(parameters_variant_type);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.target = target,
|
||||||
|
.dbus = dbus,
|
||||||
|
.bus_name = bus_name,
|
||||||
|
.object_path = object_path,
|
||||||
|
.payload_builder = payload_builder,
|
||||||
|
.parameters_builder = parameters_builder,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a parameter to the IPC call.
|
||||||
|
pub fn addParameter(self: *Self, variant: *glib.Variant) void {
|
||||||
|
self.parameters_builder.add("v", variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send the IPC to the remote Ghostty. Once it completes, nothing further
|
||||||
|
/// should be done with this object other than call `deinit`.
|
||||||
|
pub fn send(self: *Self) (std.posix.WriteError || apprt.ipc.Errors)!void {
|
||||||
|
// finish building the parameters
|
||||||
|
const parameters = self.parameters_builder.end();
|
||||||
|
|
||||||
|
// Add the parameters to the payload.
|
||||||
|
self.payload_builder.addValue(parameters);
|
||||||
|
|
||||||
|
// Add the platform data to the payload.
|
||||||
|
{
|
||||||
|
const platform_data_variant_type = glib.VariantType.new("a{sv}");
|
||||||
|
defer platform_data_variant_type.free();
|
||||||
|
|
||||||
|
self.payload_builder.open(platform_data_variant_type);
|
||||||
|
defer self.payload_builder.close();
|
||||||
|
|
||||||
|
// We have no platform data.
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = self.payload_builder.end();
|
||||||
|
|
||||||
|
{
|
||||||
|
var err_: ?*glib.Error = null;
|
||||||
|
defer if (err_) |err| err.free();
|
||||||
|
|
||||||
|
const result_ = self.dbus.callSync(
|
||||||
|
self.bus_name,
|
||||||
|
self.object_path,
|
||||||
|
"org.gtk.Actions",
|
||||||
|
"Activate",
|
||||||
|
payload,
|
||||||
|
null, // We don't care about the return type, we don't do anything with it.
|
||||||
|
.{}, // no flags
|
||||||
|
-1, // default timeout
|
||||||
|
null, // not cancellable
|
||||||
|
&err_,
|
||||||
|
);
|
||||||
|
defer if (result_) |result| result.unref();
|
||||||
|
|
||||||
|
if (err_) |err| {
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
try stderr.print(
|
||||||
|
"D-Bus method call returned an error err={s}\n",
|
||||||
|
.{err.f_message orelse "(unknown)"},
|
||||||
|
);
|
||||||
|
return error.IPCFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Free/unref any data held by this instance.
|
||||||
|
pub fn deinit(self: *Self, alloc: Allocator) void {
|
||||||
|
switch (self.target) {
|
||||||
|
.class => alloc.free(self.object_path),
|
||||||
|
.detect => {},
|
||||||
|
}
|
||||||
|
self.parameters_builder.unref();
|
||||||
|
self.payload_builder.unref();
|
||||||
|
self.dbus.unref();
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const gio = @import("gio");
|
|
||||||
const glib = @import("glib");
|
const glib = @import("glib");
|
||||||
|
|
||||||
const apprt = @import("../../../apprt.zig");
|
const apprt = @import("../../../apprt.zig");
|
||||||
const ApprtApp = @import("../App.zig");
|
const DBus = @import("DBus.zig");
|
||||||
|
|
||||||
// Use a D-Bus method call to open a new window on GTK.
|
// Use a D-Bus method call to open a new window on GTK.
|
||||||
// See: https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
|
// See: https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
|
||||||
|
|
@ -22,149 +21,42 @@ const ApprtApp = @import("../App.zig");
|
||||||
// gdbus call --session --dest com.mitchellh.ghostty --object-path /com/mitchellh/ghostty --method org.gtk.Actions.Activate new-window-command '[<@as ["echo" "hello"]>]' []
|
// gdbus call --session --dest com.mitchellh.ghostty --object-path /com/mitchellh/ghostty --method org.gtk.Actions.Activate new-window-command '[<@as ["echo" "hello"]>]' []
|
||||||
// ```
|
// ```
|
||||||
pub fn newWindow(alloc: Allocator, target: apprt.ipc.Target, value: apprt.ipc.Action.NewWindow) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!bool {
|
pub fn newWindow(alloc: Allocator, target: apprt.ipc.Target, value: apprt.ipc.Action.NewWindow) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!bool {
|
||||||
const stderr = std.io.getStdErr().writer();
|
var dbus = try DBus.init(
|
||||||
|
alloc,
|
||||||
|
target,
|
||||||
|
if (value.arguments == null)
|
||||||
|
"new-window"
|
||||||
|
else
|
||||||
|
"new-window-command",
|
||||||
|
);
|
||||||
|
defer dbus.deinit(alloc);
|
||||||
|
|
||||||
// Get the appropriate bus name and object path for contacting the
|
if (value.arguments) |arguments| {
|
||||||
// Ghostty instance we're interested in.
|
// If `-e` was specified on the command line, the first
|
||||||
const bus_name: [:0]const u8, const object_path: [:0]const u8 = switch (target) {
|
// parameter is an array of strings that contain the arguments
|
||||||
.class => |class| result: {
|
// that came after `-e`, which will be interpreted as a command
|
||||||
// Force the usage of the class specified on the CLI to determine the
|
// to run.
|
||||||
// bus name and object path.
|
const as_variant_type = glib.VariantType.new("as");
|
||||||
const object_path = try std.fmt.allocPrintZ(alloc, "/{s}", .{class});
|
defer as_variant_type.free();
|
||||||
|
|
||||||
std.mem.replaceScalar(u8, object_path, '.', '/');
|
const s_variant_type = glib.VariantType.new("s");
|
||||||
std.mem.replaceScalar(u8, object_path, '-', '_');
|
defer s_variant_type.free();
|
||||||
|
|
||||||
break :result .{ class, object_path };
|
var command: glib.VariantBuilder = undefined;
|
||||||
},
|
command.init(as_variant_type);
|
||||||
.detect => .{ ApprtApp.application_id, ApprtApp.object_path },
|
errdefer command.clear();
|
||||||
};
|
|
||||||
defer {
|
for (arguments) |argument| {
|
||||||
switch (target) {
|
const bytes = glib.Bytes.new(argument.ptr, argument.len + 1);
|
||||||
.class => alloc.free(object_path),
|
defer bytes.unref();
|
||||||
.detect => {},
|
const string = glib.Variant.newFromBytes(s_variant_type, bytes, @intFromBool(true));
|
||||||
|
command.addValue(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbus.addParameter(command.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gio.Application.idIsValid(bus_name.ptr) == 0) {
|
try dbus.send();
|
||||||
try stderr.print("D-Bus bus name is not valid: {s}\n", .{bus_name});
|
|
||||||
return error.IPCFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (glib.Variant.isObjectPath(object_path.ptr) == 0) {
|
|
||||||
try stderr.print("D-Bus object path is not valid: {s}\n", .{object_path});
|
|
||||||
return error.IPCFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dbus = dbus: {
|
|
||||||
var err_: ?*glib.Error = null;
|
|
||||||
defer if (err_) |err| err.free();
|
|
||||||
|
|
||||||
const dbus_ = gio.busGetSync(.session, null, &err_);
|
|
||||||
if (err_) |err| {
|
|
||||||
try stderr.print(
|
|
||||||
"Unable to establish connection to D-Bus session bus: {s}\n",
|
|
||||||
.{err.f_message orelse "(unknown)"},
|
|
||||||
);
|
|
||||||
return error.IPCFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :dbus dbus_ orelse {
|
|
||||||
try stderr.print("gio.busGetSync returned null\n", .{});
|
|
||||||
return error.IPCFailed;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
defer dbus.unref();
|
|
||||||
|
|
||||||
// use a builder to create the D-Bus method call payload
|
|
||||||
const payload = payload: {
|
|
||||||
const payload_variant_type = glib.VariantType.new("(sava{sv})");
|
|
||||||
defer glib.free(payload_variant_type);
|
|
||||||
|
|
||||||
// Initialize our builder to build up our parameters
|
|
||||||
var builder: glib.VariantBuilder = undefined;
|
|
||||||
builder.init(payload_variant_type);
|
|
||||||
errdefer builder.clear();
|
|
||||||
|
|
||||||
// action
|
|
||||||
if (value.arguments == null) {
|
|
||||||
builder.add("s", "new-window");
|
|
||||||
} else {
|
|
||||||
builder.add("s", "new-window-command");
|
|
||||||
}
|
|
||||||
|
|
||||||
// parameters
|
|
||||||
{
|
|
||||||
const av_variant_type = glib.VariantType.new("av");
|
|
||||||
defer av_variant_type.free();
|
|
||||||
|
|
||||||
var parameters: glib.VariantBuilder = undefined;
|
|
||||||
parameters.init(av_variant_type);
|
|
||||||
errdefer parameters.clear();
|
|
||||||
|
|
||||||
if (value.arguments) |arguments| {
|
|
||||||
// If `-e` was specified on the command line, the first
|
|
||||||
// parameter is an array of strings that contain the arguments
|
|
||||||
// that came after `-e`, which will be interpreted as a command
|
|
||||||
// to run.
|
|
||||||
{
|
|
||||||
const as = glib.VariantType.new("as");
|
|
||||||
defer as.free();
|
|
||||||
|
|
||||||
var command: glib.VariantBuilder = undefined;
|
|
||||||
command.init(as);
|
|
||||||
errdefer command.clear();
|
|
||||||
|
|
||||||
for (arguments) |argument| {
|
|
||||||
command.add("s", argument.ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters.add("v", command.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.addValue(parameters.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const platform_data_variant_type = glib.VariantType.new("a{sv}");
|
|
||||||
defer platform_data_variant_type.free();
|
|
||||||
|
|
||||||
builder.open(platform_data_variant_type);
|
|
||||||
defer builder.close();
|
|
||||||
|
|
||||||
// we have no platform data
|
|
||||||
}
|
|
||||||
|
|
||||||
break :payload builder.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
var err_: ?*glib.Error = null;
|
|
||||||
defer if (err_) |err| err.free();
|
|
||||||
|
|
||||||
const result_ = dbus.callSync(
|
|
||||||
bus_name,
|
|
||||||
object_path,
|
|
||||||
"org.gtk.Actions",
|
|
||||||
"Activate",
|
|
||||||
payload,
|
|
||||||
null, // We don't care about the return type, we don't do anything with it.
|
|
||||||
.{}, // no flags
|
|
||||||
-1, // default timeout
|
|
||||||
null, // not cancellable
|
|
||||||
&err_,
|
|
||||||
);
|
|
||||||
defer if (result_) |result| result.unref();
|
|
||||||
|
|
||||||
if (err_) |err| {
|
|
||||||
try stderr.print(
|
|
||||||
"D-Bus method call returned an error err={s}\n",
|
|
||||||
.{err.f_message orelse "(unknown)"},
|
|
||||||
);
|
|
||||||
return error.IPCFailed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,10 @@
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
using Adw 1;
|
|
||||||
|
|
||||||
template $GhosttyInspectorWidget: Adw.Bin {
|
template $GhosttyInspectorWidget: $GhosttyImguiWidget {
|
||||||
styles [
|
styles [
|
||||||
"inspector",
|
"inspector",
|
||||||
]
|
]
|
||||||
|
|
||||||
hexpand: true;
|
hexpand: true;
|
||||||
vexpand: true;
|
vexpand: true;
|
||||||
|
|
||||||
Adw.Bin {
|
|
||||||
$GhosttyImguiWidget imgui_widget {
|
|
||||||
render => $imgui_render();
|
|
||||||
setup => $imgui_setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
|
||||||
const f = self.data_f orelse return;
|
const f = self.data_f orelse return;
|
||||||
var r = std.io.bufferedReader(f.reader());
|
var r = std.io.bufferedReader(f.reader());
|
||||||
|
|
||||||
var p: terminalpkg.Parser = .{};
|
var p: terminalpkg.Parser = .init();
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ pub fn create(
|
||||||
.cols = opts.@"terminal-cols",
|
.cols = opts.@"terminal-cols",
|
||||||
}),
|
}),
|
||||||
.handler = .{ .t = &ptr.terminal },
|
.handler = .{ .t = &ptr.terminal },
|
||||||
.stream = .{ .handler = &ptr.handler },
|
.stream = .init(&ptr.handler),
|
||||||
};
|
};
|
||||||
|
|
||||||
return ptr;
|
return ptr;
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ patch_rpath: ?[]const u8 = null,
|
||||||
flatpak: bool = false,
|
flatpak: bool = false,
|
||||||
emit_bench: bool = false,
|
emit_bench: bool = false,
|
||||||
emit_docs: bool = false,
|
emit_docs: bool = false,
|
||||||
|
emit_exe: bool = false,
|
||||||
emit_helpgen: bool = false,
|
emit_helpgen: bool = false,
|
||||||
emit_macos_app: bool = false,
|
emit_macos_app: bool = false,
|
||||||
emit_terminfo: bool = false,
|
emit_terminfo: bool = false,
|
||||||
|
|
@ -286,6 +287,12 @@ pub fn init(b: *std.Build) !Config {
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Artifacts to Emit
|
// Artifacts to Emit
|
||||||
|
|
||||||
|
config.emit_exe = b.option(
|
||||||
|
bool,
|
||||||
|
"emit-exe",
|
||||||
|
"Build and install main executables with 'build'",
|
||||||
|
) orelse true;
|
||||||
|
|
||||||
config.emit_test_exe = b.option(
|
config.emit_test_exe = b.option(
|
||||||
bool,
|
bool,
|
||||||
"emit-test-exe",
|
"emit-test-exe",
|
||||||
|
|
@ -460,6 +467,22 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a baseline CPU target retaining all the other CPU configs.
|
||||||
|
pub fn baselineTarget(self: *const Config) std.Build.ResolvedTarget {
|
||||||
|
// Set our cpu model as baseline. There may need to be other modifications
|
||||||
|
// we need to make such as resetting CPU features but for now this works.
|
||||||
|
var q = self.target.query;
|
||||||
|
q.cpu_model = .baseline;
|
||||||
|
|
||||||
|
// Same logic as build.resolveTargetQuery but we don't need to
|
||||||
|
// handle the native case.
|
||||||
|
return .{
|
||||||
|
.query = q,
|
||||||
|
.result = std.zig.system.resolveTargetQuery(q) catch
|
||||||
|
@panic("unable to resolve baseline query"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Rehydrate our Config from the comptime options. Note that not all
|
/// Rehydrate our Config from the comptime options. Note that not all
|
||||||
/// options are available at comptime, so look closely at this implementation
|
/// options are available at comptime, so look closely at this implementation
|
||||||
/// to see what is and isn't available.
|
/// to see what is and isn't available.
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ const zf = @import("zf");
|
||||||
// scroll position for larger lists.
|
// scroll position for larger lists.
|
||||||
const SMALL_LIST_THRESHOLD = 10;
|
const SMALL_LIST_THRESHOLD = 10;
|
||||||
|
|
||||||
|
const ColorScheme = enum { all, dark, light };
|
||||||
|
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
/// If true, print the full path to the theme.
|
/// If true, print the full path to the theme.
|
||||||
path: bool = false,
|
path: bool = false,
|
||||||
|
|
@ -25,7 +27,7 @@ pub const Options = struct {
|
||||||
plain: bool = false,
|
plain: bool = false,
|
||||||
|
|
||||||
/// Specifies the color scheme of the themes to include in the list.
|
/// Specifies the color scheme of the themes to include in the list.
|
||||||
color: enum { all, dark, light } = .all,
|
color: ColorScheme = .all,
|
||||||
|
|
||||||
pub fn deinit(self: Options) void {
|
pub fn deinit(self: Options) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
|
|
@ -146,28 +148,11 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
||||||
const path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name });
|
const path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name });
|
||||||
// if there is no need to filter just append the theme to the list
|
try themes.append(.{
|
||||||
if (opts.color == .all) {
|
.path = path,
|
||||||
try themes.append(.{
|
.location = loc.location,
|
||||||
.path = path,
|
.theme = try alloc.dupe(u8, entry.name),
|
||||||
.location = loc.location,
|
});
|
||||||
.theme = try alloc.dupe(u8, entry.name),
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise check if the theme should be included based on the provided options
|
|
||||||
var config = try Config.default(alloc);
|
|
||||||
defer config.deinit();
|
|
||||||
try config.loadFile(config._arena.?.allocator(), path);
|
|
||||||
|
|
||||||
if (shouldIncludeTheme(opts, config)) {
|
|
||||||
try themes.append(.{
|
|
||||||
.path = path,
|
|
||||||
.location = loc.location,
|
|
||||||
.theme = try alloc.dupe(u8, entry.name),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +167,7 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
|
||||||
std.mem.sortUnstable(ThemeListElement, themes.items, {}, ThemeListElement.lessThan);
|
std.mem.sortUnstable(ThemeListElement, themes.items, {}, ThemeListElement.lessThan);
|
||||||
|
|
||||||
if (tui.can_pretty_print and !opts.plain and std.posix.isatty(std.io.getStdOut().handle)) {
|
if (tui.can_pretty_print and !opts.plain and std.posix.isatty(std.io.getStdOut().handle)) {
|
||||||
try preview(gpa_alloc, themes.items);
|
try preview(gpa_alloc, themes.items, opts.color);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,8 +207,9 @@ const Preview = struct {
|
||||||
},
|
},
|
||||||
color_scheme: vaxis.Color.Scheme,
|
color_scheme: vaxis.Color.Scheme,
|
||||||
text_input: vaxis.widgets.TextInput,
|
text_input: vaxis.widgets.TextInput,
|
||||||
|
theme_filter: ColorScheme,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, themes: []ThemeListElement) !*Preview {
|
pub fn init(allocator: std.mem.Allocator, themes: []ThemeListElement, theme_filter: ColorScheme) !*Preview {
|
||||||
const self = try allocator.create(Preview);
|
const self = try allocator.create(Preview);
|
||||||
|
|
||||||
self.* = .{
|
self.* = .{
|
||||||
|
|
@ -240,11 +226,10 @@ const Preview = struct {
|
||||||
.mode = .normal,
|
.mode = .normal,
|
||||||
.color_scheme = .light,
|
.color_scheme = .light,
|
||||||
.text_input = vaxis.widgets.TextInput.init(allocator, &self.vx.unicode),
|
.text_input = vaxis.widgets.TextInput.init(allocator, &self.vx.unicode),
|
||||||
|
.theme_filter = theme_filter,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (0..themes.len) |i| {
|
try self.updateFiltered();
|
||||||
try self.filtered.append(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
@ -308,6 +293,8 @@ const Preview = struct {
|
||||||
|
|
||||||
self.filtered.clearRetainingCapacity();
|
self.filtered.clearRetainingCapacity();
|
||||||
|
|
||||||
|
var theme_config = try Config.default(self.allocator);
|
||||||
|
defer theme_config.deinit();
|
||||||
if (self.text_input.buf.realLength() > 0) {
|
if (self.text_input.buf.realLength() > 0) {
|
||||||
const first_half = self.text_input.buf.firstHalf();
|
const first_half = self.text_input.buf.firstHalf();
|
||||||
const second_half = self.text_input.buf.secondHalf();
|
const second_half = self.text_input.buf.secondHalf();
|
||||||
|
|
@ -328,6 +315,9 @@ const Preview = struct {
|
||||||
while (it.next()) |token| try tokens.append(token);
|
while (it.next()) |token| try tokens.append(token);
|
||||||
|
|
||||||
for (self.themes, 0..) |*theme, i| {
|
for (self.themes, 0..) |*theme, i| {
|
||||||
|
try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
|
||||||
|
if (!shouldIncludeTheme(self.theme_filter, theme_config)) continue;
|
||||||
|
|
||||||
theme.rank = zf.rank(theme.theme, tokens.items, .{
|
theme.rank = zf.rank(theme.theme, tokens.items, .{
|
||||||
.to_lower = true,
|
.to_lower = true,
|
||||||
.plain = true,
|
.plain = true,
|
||||||
|
|
@ -336,8 +326,11 @@ const Preview = struct {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (self.themes, 0..) |*theme, i| {
|
for (self.themes, 0..) |*theme, i| {
|
||||||
try self.filtered.append(i);
|
try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
|
||||||
theme.rank = null;
|
if (shouldIncludeTheme(self.theme_filter, theme_config)) {
|
||||||
|
try self.filtered.append(i);
|
||||||
|
theme.rank = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,6 +431,14 @@ const Preview = struct {
|
||||||
self.themes[self.filtered.items[self.current]].path,
|
self.themes[self.filtered.items[self.current]].path,
|
||||||
alloc,
|
alloc,
|
||||||
);
|
);
|
||||||
|
if (key.matches('f', .{})) {
|
||||||
|
switch (self.theme_filter) {
|
||||||
|
.all => self.theme_filter = .dark,
|
||||||
|
.dark => self.theme_filter = .light,
|
||||||
|
.light => self.theme_filter = .all,
|
||||||
|
}
|
||||||
|
try self.updateFiltered();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
.help => {
|
.help => {
|
||||||
if (key.matches('q', .{}))
|
if (key.matches('q', .{}))
|
||||||
|
|
@ -695,6 +696,7 @@ const Preview = struct {
|
||||||
const key_help = [_]struct { keys: []const u8, help: []const u8 }{
|
const key_help = [_]struct { keys: []const u8, help: []const u8 }{
|
||||||
.{ .keys = "^C, q, ESC", .help = "Quit." },
|
.{ .keys = "^C, q, ESC", .help = "Quit." },
|
||||||
.{ .keys = "F1, ?, ^H", .help = "Toggle help window." },
|
.{ .keys = "F1, ?, ^H", .help = "Toggle help window." },
|
||||||
|
.{ .keys = "f", .help = "Cycle through theme filters." },
|
||||||
.{ .keys = "k, ↑", .help = "Move up 1 theme." },
|
.{ .keys = "k, ↑", .help = "Move up 1 theme." },
|
||||||
.{ .keys = "ScrollUp", .help = "Move up 1 theme." },
|
.{ .keys = "ScrollUp", .help = "Move up 1 theme." },
|
||||||
.{ .keys = "PgUp", .help = "Move up 20 themes." },
|
.{ .keys = "PgUp", .help = "Move up 20 themes." },
|
||||||
|
|
@ -1615,18 +1617,17 @@ fn color(config: Config, palette: usize) vaxis.Color {
|
||||||
|
|
||||||
const lorem_ipsum = @embedFile("lorem_ipsum.txt");
|
const lorem_ipsum = @embedFile("lorem_ipsum.txt");
|
||||||
|
|
||||||
fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement) !void {
|
fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement, theme_filter: ColorScheme) !void {
|
||||||
var app = try Preview.init(allocator, themes);
|
var app = try Preview.init(allocator, themes, theme_filter);
|
||||||
defer app.deinit();
|
defer app.deinit();
|
||||||
try app.run();
|
try app.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shouldIncludeTheme(opts: Options, theme_config: Config) bool {
|
fn shouldIncludeTheme(theme_filter: ColorScheme, theme_config: Config) bool {
|
||||||
const rf = @as(f32, @floatFromInt(theme_config.background.r)) / 255.0;
|
const rf = @as(f32, @floatFromInt(theme_config.background.r)) / 255.0;
|
||||||
const gf = @as(f32, @floatFromInt(theme_config.background.g)) / 255.0;
|
const gf = @as(f32, @floatFromInt(theme_config.background.g)) / 255.0;
|
||||||
const bf = @as(f32, @floatFromInt(theme_config.background.b)) / 255.0;
|
const bf = @as(f32, @floatFromInt(theme_config.background.b)) / 255.0;
|
||||||
const luminance = 0.2126 * rf + 0.7152 * gf + 0.0722 * bf;
|
const luminance = 0.2126 * rf + 0.7152 * gf + 0.0722 * bf;
|
||||||
const is_dark = luminance < 0.5;
|
const is_dark = luminance < 0.5;
|
||||||
|
return (theme_filter == .all) or (theme_filter == .dark and is_dark) or (theme_filter == .light and !is_dark);
|
||||||
return (opts.color == .dark and is_dark) or (opts.color == .light and !is_dark);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ pub const ClipboardAccess = Config.ClipboardAccess;
|
||||||
pub const Command = Config.Command;
|
pub const Command = Config.Command;
|
||||||
pub const ConfirmCloseSurface = Config.ConfirmCloseSurface;
|
pub const ConfirmCloseSurface = Config.ConfirmCloseSurface;
|
||||||
pub const CopyOnSelect = Config.CopyOnSelect;
|
pub const CopyOnSelect = Config.CopyOnSelect;
|
||||||
|
pub const RightClickAction = Config.RightClickAction;
|
||||||
pub const CustomShaderAnimation = Config.CustomShaderAnimation;
|
pub const CustomShaderAnimation = Config.CustomShaderAnimation;
|
||||||
pub const FontSyntheticStyle = Config.FontSyntheticStyle;
|
pub const FontSyntheticStyle = Config.FontSyntheticStyle;
|
||||||
pub const FontShapingBreak = Config.FontShapingBreak;
|
pub const FontShapingBreak = Config.FontShapingBreak;
|
||||||
|
|
|
||||||
|
|
@ -592,24 +592,24 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
|
||||||
///
|
///
|
||||||
/// * `contain`
|
/// * `contain`
|
||||||
///
|
///
|
||||||
/// Preserving the aspect ratio, scale the background image to the largest
|
/// Preserving the aspect ratio, scale the background image to the largest
|
||||||
/// size that can still be contained within the terminal, so that the whole
|
/// size that can still be contained within the terminal, so that the whole
|
||||||
/// image is visible.
|
/// image is visible.
|
||||||
///
|
///
|
||||||
/// * `cover`
|
/// * `cover`
|
||||||
///
|
///
|
||||||
/// Preserving the aspect ratio, scale the background image to the smallest
|
/// Preserving the aspect ratio, scale the background image to the smallest
|
||||||
/// size that can completely cover the terminal. This may result in one or
|
/// size that can completely cover the terminal. This may result in one or
|
||||||
/// more edges of the image being clipped by the edge of the terminal.
|
/// more edges of the image being clipped by the edge of the terminal.
|
||||||
///
|
///
|
||||||
/// * `stretch`
|
/// * `stretch`
|
||||||
///
|
///
|
||||||
/// Stretch the background image to the full size of the terminal, without
|
/// Stretch the background image to the full size of the terminal, without
|
||||||
/// preserving the aspect ratio.
|
/// preserving the aspect ratio.
|
||||||
///
|
///
|
||||||
/// * `none`
|
/// * `none`
|
||||||
///
|
///
|
||||||
/// Don't scale the background image.
|
/// Don't scale the background image.
|
||||||
///
|
///
|
||||||
/// The default value is `contain`.
|
/// The default value is `contain`.
|
||||||
///
|
///
|
||||||
|
|
@ -1330,53 +1330,59 @@ class: ?[:0]const u8 = null,
|
||||||
/// The keybind trigger can be prefixed with some special values to change
|
/// The keybind trigger can be prefixed with some special values to change
|
||||||
/// the behavior of the keybind. These are:
|
/// the behavior of the keybind. These are:
|
||||||
///
|
///
|
||||||
/// * `all:` - Make the keybind apply to all terminal surfaces. By default,
|
/// * `all:`
|
||||||
/// keybinds only apply to the focused terminal surface. If this is true,
|
|
||||||
/// then the keybind will be sent to all terminal surfaces. This only
|
|
||||||
/// applies to actions that are surface-specific. For actions that
|
|
||||||
/// are already global (e.g. `quit`), this prefix has no effect.
|
|
||||||
///
|
///
|
||||||
/// Available since: 1.0.0
|
/// Make the keybind apply to all terminal surfaces. By default,
|
||||||
|
/// keybinds only apply to the focused terminal surface. If this is true,
|
||||||
|
/// then the keybind will be sent to all terminal surfaces. This only
|
||||||
|
/// applies to actions that are surface-specific. For actions that
|
||||||
|
/// are already global (e.g. `quit`), this prefix has no effect.
|
||||||
///
|
///
|
||||||
/// * `global:` - Make the keybind global. By default, keybinds only work
|
/// Available since: 1.0.0
|
||||||
/// within Ghostty and under the right conditions (application focused,
|
|
||||||
/// sometimes terminal focused, etc.). If you want a keybind to work
|
|
||||||
/// globally across your system (e.g. even when Ghostty is not focused),
|
|
||||||
/// specify this prefix. This prefix implies `all:`. Note: this does not
|
|
||||||
/// work in all environments; see the additional notes below for more
|
|
||||||
/// information.
|
|
||||||
///
|
///
|
||||||
/// Available since: 1.0.0 (on macOS)
|
/// * `global:`
|
||||||
/// Available since: 1.2.0 (on GTK)
|
|
||||||
///
|
///
|
||||||
/// * `unconsumed:` - Do not consume the input. By default, a keybind
|
/// Make the keybind global. By default, keybinds only work within Ghostty
|
||||||
/// will consume the input, meaning that the associated encoding (if
|
/// and under the right conditions (application focused, sometimes terminal
|
||||||
/// any) will not be sent to the running program in the terminal. If
|
/// focused, etc.). If you want a keybind to work globally across your system
|
||||||
/// you wish to send the encoded value to the program, specify the
|
/// (e.g. even when Ghostty is not focused), specify this prefix.
|
||||||
/// `unconsumed:` prefix before the entire keybind. For example:
|
/// This prefix implies `all:`.
|
||||||
/// `unconsumed:ctrl+a=reload_config`. `global:` and `all:`-prefixed
|
|
||||||
/// keybinds will always consume the input regardless of this setting.
|
|
||||||
/// Since they are not associated with a specific terminal surface,
|
|
||||||
/// they're never encoded.
|
|
||||||
///
|
///
|
||||||
/// Available since: 1.0.0
|
/// Note: this does not work in all environments; see the additional notes
|
||||||
|
/// below for more information.
|
||||||
///
|
///
|
||||||
/// * `performable:` - Only consume the input if the action is able to be
|
/// Available since: 1.0.0 on macOS, 1.2.0 on GTK
|
||||||
/// performed. For example, the `copy_to_clipboard` action will only
|
|
||||||
/// consume the input if there is a selection to copy. If there is no
|
|
||||||
/// selection, Ghostty behaves as if the keybind was not set. This has
|
|
||||||
/// no effect with `global:` or `all:`-prefixed keybinds. For key
|
|
||||||
/// sequences, this will reset the sequence if the action is not
|
|
||||||
/// performable (acting identically to not having a keybind set at
|
|
||||||
/// all).
|
|
||||||
///
|
///
|
||||||
/// Performable keybinds will not appear as menu shortcuts in the
|
/// * `unconsumed:`
|
||||||
/// application menu. This is because the menu shortcuts force the
|
|
||||||
/// action to be performed regardless of the state of the terminal.
|
|
||||||
/// Performable keybinds will still work, they just won't appear as
|
|
||||||
/// a shortcut label in the menu.
|
|
||||||
///
|
///
|
||||||
/// Available since: 1.1.0
|
/// Do not consume the input. By default, a keybind will consume the input,
|
||||||
|
/// meaning that the associated encoding (if any) will not be sent to the
|
||||||
|
/// running program in the terminal. If you wish to send the encoded value
|
||||||
|
/// to the program, specify the `unconsumed:` prefix before the entire
|
||||||
|
/// keybind. For example: `unconsumed:ctrl+a=reload_config`. `global:` and
|
||||||
|
/// `all:`-prefixed keybinds will always consume the input regardless of
|
||||||
|
/// this setting. Since they are not associated with a specific terminal
|
||||||
|
/// surface, they're never encoded.
|
||||||
|
///
|
||||||
|
/// Available since: 1.0.0
|
||||||
|
///
|
||||||
|
/// * `performable:`
|
||||||
|
///
|
||||||
|
/// Only consume the input if the action is able to be performed.
|
||||||
|
/// For example, the `copy_to_clipboard` action will only consume the input
|
||||||
|
/// if there is a selection to copy. If there is no selection, Ghostty
|
||||||
|
/// behaves as if the keybind was not set. This has no effect with `global:`
|
||||||
|
/// or `all:`-prefixed keybinds. For key sequences, this will reset the
|
||||||
|
/// sequence if the action is not performable (acting identically to not
|
||||||
|
/// having a keybind set at all).
|
||||||
|
///
|
||||||
|
/// Performable keybinds will not appear as menu shortcuts in the
|
||||||
|
/// application menu. This is because the menu shortcuts force the
|
||||||
|
/// action to be performed regardless of the state of the terminal.
|
||||||
|
/// Performable keybinds will still work, they just won't appear as
|
||||||
|
/// a shortcut label in the menu.
|
||||||
|
///
|
||||||
|
/// Available since: 1.1.0
|
||||||
///
|
///
|
||||||
/// Keybind triggers are not unique per prefix combination. For example,
|
/// Keybind triggers are not unique per prefix combination. For example,
|
||||||
/// `ctrl+a` and `global:ctrl+a` are not two separate keybinds. The keybind
|
/// `ctrl+a` and `global:ctrl+a` are not two separate keybinds. The keybind
|
||||||
|
|
@ -1522,28 +1528,36 @@ keybind: Keybinds = .{},
|
||||||
///
|
///
|
||||||
/// Valid values:
|
/// Valid values:
|
||||||
///
|
///
|
||||||
/// * `none` - All window decorations will be disabled. Titlebar,
|
/// * `none`
|
||||||
/// borders, etc. will not be shown. On macOS, this will also disable
|
|
||||||
/// tabs (enforced by the system).
|
|
||||||
///
|
///
|
||||||
/// * `auto` - Automatically decide to use either client-side or server-side
|
/// All window decorations will be disabled. Titlebar, borders, etc. will
|
||||||
/// decorations based on the detected preferences of the current OS and
|
/// not be shown. On macOS, this will also disable tabs (enforced by the
|
||||||
/// desktop environment. This option usually makes Ghostty look the most
|
/// system).
|
||||||
/// "native" for your desktop.
|
|
||||||
///
|
///
|
||||||
/// * `client` - Prefer client-side decorations.
|
/// * `auto`
|
||||||
///
|
///
|
||||||
/// Available since: 1.1.0
|
/// Automatically decide to use either client-side or server-side
|
||||||
|
/// decorations based on the detected preferences of the current OS and
|
||||||
|
/// desktop environment. This option usually makes Ghostty look the most
|
||||||
|
/// "native" for your desktop.
|
||||||
///
|
///
|
||||||
/// * `server` - Prefer server-side decorations. This is only relevant
|
/// * `client`
|
||||||
/// on Linux with GTK, either on X11, or Wayland on a compositor that
|
|
||||||
/// supports the `org_kde_kwin_server_decoration` protocol (e.g. KDE Plasma,
|
|
||||||
/// but almost any non-GNOME desktop supports this protocol).
|
|
||||||
///
|
///
|
||||||
/// If `server` is set but the environment doesn't support server-side
|
/// Prefer client-side decorations.
|
||||||
/// decorations, client-side decorations will be used instead.
|
|
||||||
///
|
///
|
||||||
/// Available since: 1.1.0
|
/// Available since: 1.1.0
|
||||||
|
///
|
||||||
|
/// * `server`
|
||||||
|
///
|
||||||
|
/// Prefer server-side decorations. This is only relevant on Linux with GTK,
|
||||||
|
/// either on X11, or Wayland on a compositor that supports the
|
||||||
|
/// `org_kde_kwin_server_decoration` protocol (e.g. KDE Plasma, but almost
|
||||||
|
/// any non-GNOME desktop supports this protocol).
|
||||||
|
///
|
||||||
|
/// If `server` is set but the environment doesn't support server-side
|
||||||
|
/// decorations, client-side decorations will be used instead.
|
||||||
|
///
|
||||||
|
/// Available since: 1.1.0
|
||||||
///
|
///
|
||||||
/// The default value is `auto`.
|
/// The default value is `auto`.
|
||||||
///
|
///
|
||||||
|
|
@ -1886,6 +1900,19 @@ keybind: Keybinds = .{},
|
||||||
else => .false,
|
else => .false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// The action to take when the user right-clicks on the terminal surface.
|
||||||
|
///
|
||||||
|
/// Valid values:
|
||||||
|
/// * `context-menu` - Show the context menu.
|
||||||
|
/// * `paste` - Paste the contents of the clipboard.
|
||||||
|
/// * `copy` - Copy the selected text to the clipboard.
|
||||||
|
/// * `copy-or-paste` - If there is a selection, copy the selected text to
|
||||||
|
/// the clipboard; otherwise, paste the contents of the clipboard.
|
||||||
|
/// * `ignore` - Do nothing, ignore the right-click.
|
||||||
|
///
|
||||||
|
/// The default value is `context-menu`.
|
||||||
|
@"right-click-action": RightClickAction = .@"context-menu",
|
||||||
|
|
||||||
/// The time in milliseconds between clicks to consider a click a repeat
|
/// The time in milliseconds between clicks to consider a click a repeat
|
||||||
/// (double, triple, etc.) or an entirely new single click. A value of zero will
|
/// (double, triple, etc.) or an entirely new single click. A value of zero will
|
||||||
/// use a platform-specific default. The default on macOS is determined by the
|
/// use a platform-specific default. The default on macOS is determined by the
|
||||||
|
|
@ -2316,9 +2343,9 @@ keybind: Keybinds = .{},
|
||||||
///
|
///
|
||||||
/// * `sampler2D iChannel0` - Input texture.
|
/// * `sampler2D iChannel0` - Input texture.
|
||||||
///
|
///
|
||||||
/// A texture containing the current terminal screen. If multiple custom
|
/// A texture containing the current terminal screen. If multiple custom
|
||||||
/// shaders are specified, the output of previous shaders is written to
|
/// shaders are specified, the output of previous shaders is written to
|
||||||
/// this texture, to allow combining multiple effects.
|
/// this texture, to allow combining multiple effects.
|
||||||
///
|
///
|
||||||
/// * `vec3 iResolution` - Output texture size, `[width, height, 1]` (in px).
|
/// * `vec3 iResolution` - Output texture size, `[width, height, 1]` (in px).
|
||||||
///
|
///
|
||||||
|
|
@ -2604,6 +2631,21 @@ keybind: Keybinds = .{},
|
||||||
/// editor, etc.
|
/// editor, etc.
|
||||||
@"macos-titlebar-proxy-icon": MacTitlebarProxyIcon = .visible,
|
@"macos-titlebar-proxy-icon": MacTitlebarProxyIcon = .visible,
|
||||||
|
|
||||||
|
/// Controls the windowing behavior when dropping a file or folder
|
||||||
|
/// onto the Ghostty icon in the macOS dock.
|
||||||
|
///
|
||||||
|
/// Valid values are:
|
||||||
|
///
|
||||||
|
/// * `new-tab` - Create a new tab in the current window, or open
|
||||||
|
/// a new window if none exist.
|
||||||
|
/// * `new-window` - Create a new window unconditionally.
|
||||||
|
///
|
||||||
|
/// The default value is `new-tab`.
|
||||||
|
///
|
||||||
|
/// This setting is only supported on macOS and has no effect on other
|
||||||
|
/// platforms.
|
||||||
|
@"macos-dock-drop-behavior": MacOSDockDropBehavior = .@"new-tab",
|
||||||
|
|
||||||
/// macOS doesn't have a distinct "alt" key and instead has the "option"
|
/// macOS doesn't have a distinct "alt" key and instead has the "option"
|
||||||
/// key which behaves slightly differently. On macOS by default, the
|
/// key which behaves slightly differently. On macOS by default, the
|
||||||
/// option key plus a character will sometimes produce a Unicode character.
|
/// option key plus a character will sometimes produce a Unicode character.
|
||||||
|
|
@ -2708,6 +2750,8 @@ keybind: Keybinds = .{},
|
||||||
/// * `blueprint`, `chalkboard`, `microchip`, `glass`, `holographic`,
|
/// * `blueprint`, `chalkboard`, `microchip`, `glass`, `holographic`,
|
||||||
/// `paper`, `retro`, `xray` - Official variants of the Ghostty icon
|
/// `paper`, `retro`, `xray` - Official variants of the Ghostty icon
|
||||||
/// hand-created by artists (no AI).
|
/// hand-created by artists (no AI).
|
||||||
|
/// * `custom` - Use a completely custom icon. The location must be specified
|
||||||
|
/// using the additional `macos-custom-icon` configuration
|
||||||
/// * `custom-style` - Use the official Ghostty icon but with custom
|
/// * `custom-style` - Use the official Ghostty icon but with custom
|
||||||
/// styles applied to various layers. The custom styles must be
|
/// styles applied to various layers. The custom styles must be
|
||||||
/// specified using the additional `macos-icon`-prefixed configurations.
|
/// specified using the additional `macos-icon`-prefixed configurations.
|
||||||
|
|
@ -2726,6 +2770,15 @@ keybind: Keybinds = .{},
|
||||||
/// effort.
|
/// effort.
|
||||||
@"macos-icon": MacAppIcon = .official,
|
@"macos-icon": MacAppIcon = .official,
|
||||||
|
|
||||||
|
/// The absolute path to the custom icon file.
|
||||||
|
/// Supported formats include PNG, JPEG, and ICNS.
|
||||||
|
///
|
||||||
|
/// Defaults to `~/.config/ghostty/Ghostty.icns`
|
||||||
|
///
|
||||||
|
/// Note: This configuration is required when `macos-icon` is set to
|
||||||
|
/// `custom`
|
||||||
|
@"macos-custom-icon": ?[]const u8 = null,
|
||||||
|
|
||||||
/// The material to use for the frame of the macOS app icon.
|
/// The material to use for the frame of the macOS app icon.
|
||||||
///
|
///
|
||||||
/// Valid values:
|
/// Valid values:
|
||||||
|
|
@ -6695,6 +6748,25 @@ pub const CopyOnSelect = enum {
|
||||||
clipboard,
|
clipboard,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Options for right-click actions.
|
||||||
|
pub const RightClickAction = enum {
|
||||||
|
/// No action is taken on right-click.
|
||||||
|
ignore,
|
||||||
|
|
||||||
|
/// Pastes from the system clipboard.
|
||||||
|
paste,
|
||||||
|
|
||||||
|
/// Copies the selected text to the system clipboard.
|
||||||
|
copy,
|
||||||
|
|
||||||
|
/// Copies the selected text to the system clipboard and
|
||||||
|
/// pastes the clipboard if no text is selected.
|
||||||
|
@"copy-or-paste",
|
||||||
|
|
||||||
|
/// Shows a context menu with options.
|
||||||
|
@"context-menu",
|
||||||
|
};
|
||||||
|
|
||||||
/// Shell integration values
|
/// Shell integration values
|
||||||
pub const ShellIntegration = enum {
|
pub const ShellIntegration = enum {
|
||||||
none,
|
none,
|
||||||
|
|
@ -6929,6 +7001,7 @@ pub const MacAppIcon = enum {
|
||||||
paper,
|
paper,
|
||||||
retro,
|
retro,
|
||||||
xray,
|
xray,
|
||||||
|
custom,
|
||||||
@"custom-style",
|
@"custom-style",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -7024,6 +7097,12 @@ pub const WindowNewTabPosition = enum {
|
||||||
end,
|
end,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// See macos-dock-drop-behavior
|
||||||
|
pub const MacOSDockDropBehavior = enum {
|
||||||
|
@"new-tab",
|
||||||
|
window,
|
||||||
|
};
|
||||||
|
|
||||||
/// See window-show-tab-bar
|
/// See window-show-tab-bar
|
||||||
pub const WindowShowTabBar = enum {
|
pub const WindowShowTabBar = enum {
|
||||||
always,
|
always,
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,8 @@ pub const FileFormatter = struct {
|
||||||
opts: std.fmt.FormatOptions,
|
opts: std.fmt.FormatOptions,
|
||||||
writer: anytype,
|
writer: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
|
@setEvalBranchQuota(10_000);
|
||||||
|
|
||||||
_ = layout;
|
_ = layout;
|
||||||
_ = opts;
|
_ = opts;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -937,6 +937,9 @@ test init {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "add full" {
|
test "add full" {
|
||||||
|
// This test is way too slow to run under Valgrind, unfortunately.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
|
||||||
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const testFont = font.embedded.regular;
|
const testFont = font.embedded.regular;
|
||||||
|
|
|
||||||
|
|
@ -413,6 +413,7 @@ test "fontconfig" {
|
||||||
// Get a deferred face from fontconfig
|
// Get a deferred face from fontconfig
|
||||||
var def = def: {
|
var def = def: {
|
||||||
var fc = discovery.Fontconfig.init();
|
var fc = discovery.Fontconfig.init();
|
||||||
|
defer fc.deinit();
|
||||||
var it = try fc.discover(alloc, .{ .family = "monospace", .size = 12 });
|
var it = try fc.discover(alloc, .{ .family = "monospace", .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
break :def (try it.next()).?;
|
break :def (try it.next()).?;
|
||||||
|
|
|
||||||
|
|
@ -897,6 +897,7 @@ test "fontconfig" {
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
var fc = Fontconfig.init();
|
var fc = Fontconfig.init();
|
||||||
|
defer fc.deinit();
|
||||||
var it = try fc.discover(alloc, .{ .family = "monospace", .size = 12 });
|
var it = try fc.discover(alloc, .{ .family = "monospace", .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
}
|
}
|
||||||
|
|
@ -908,12 +909,14 @@ test "fontconfig codepoint" {
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
var fc = Fontconfig.init();
|
var fc = Fontconfig.init();
|
||||||
|
defer fc.deinit();
|
||||||
var it = try fc.discover(alloc, .{ .codepoint = 'A', .size = 12 });
|
var it = try fc.discover(alloc, .{ .codepoint = 'A', .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
|
|
||||||
// The first result should have the codepoint. Later ones may not
|
// The first result should have the codepoint. Later ones may not
|
||||||
// because fontconfig returns all fonts sorted.
|
// because fontconfig returns all fonts sorted.
|
||||||
const face = (try it.next()).?;
|
var face = (try it.next()).?;
|
||||||
|
defer face.deinit();
|
||||||
try testing.expect(face.hasCodepoint('A', null));
|
try testing.expect(face.hasCodepoint('A', null));
|
||||||
|
|
||||||
// Should have other codepoints too
|
// Should have other codepoints too
|
||||||
|
|
|
||||||
|
|
@ -408,18 +408,15 @@ pub const Face = struct {
|
||||||
const px_x: i32 = @intFromFloat(@floor(x));
|
const px_x: i32 = @intFromFloat(@floor(x));
|
||||||
const px_y: i32 = @intFromFloat(@floor(y));
|
const px_y: i32 = @intFromFloat(@floor(y));
|
||||||
|
|
||||||
// We offset our glyph by its bearings when we draw it, so that it's
|
// We keep track of the fractional part of the pixel bearings, which
|
||||||
// rendered fully inside our canvas area, but we make sure to keep the
|
// we will add as an offset when rasterizing to make sure we get the
|
||||||
// fractional pixel offset so that we rasterize with the appropriate
|
// correct sub-pixel position.
|
||||||
// sub-pixel position.
|
|
||||||
const frac_x = x - @floor(x);
|
const frac_x = x - @floor(x);
|
||||||
const frac_y = y - @floor(y);
|
const frac_y = y - @floor(y);
|
||||||
const draw_x = -rect.origin.x + frac_x;
|
|
||||||
const draw_y = -rect.origin.y + frac_y;
|
|
||||||
|
|
||||||
// Add the fractional pixel to the width and height and take
|
// Add the fractional pixel to the width and height and take
|
||||||
// the ceiling to get a canvas size that will definitely fit
|
// the ceiling to get a canvas size that will definitely fit
|
||||||
// our drawn glyph.
|
// our drawn glyph, including the fractional offset.
|
||||||
const px_width: u32 = @intFromFloat(@ceil(width + frac_x));
|
const px_width: u32 = @intFromFloat(@ceil(width + frac_x));
|
||||||
const px_height: u32 = @intFromFloat(@ceil(height + frac_y));
|
const px_height: u32 = @intFromFloat(@ceil(height + frac_y));
|
||||||
|
|
||||||
|
|
@ -525,6 +522,17 @@ pub const Face = struct {
|
||||||
context.setLineWidth(ctx, line_width);
|
context.setLineWidth(ctx, line_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translate our drawing context so that when we draw our
|
||||||
|
// glyph the bottom/left edge is at the correct sub-pixel
|
||||||
|
// position. The bottom/left edges are guaranteed to be at
|
||||||
|
// exactly [0, 0] relative to this because when we call to
|
||||||
|
// `drawGlyphs`, we pass the negated bearings.
|
||||||
|
context.translateCTM(
|
||||||
|
ctx,
|
||||||
|
frac_x,
|
||||||
|
frac_y,
|
||||||
|
);
|
||||||
|
|
||||||
// Scale the drawing context so that when we draw
|
// Scale the drawing context so that when we draw
|
||||||
// our glyph it's stretched to the constrained size.
|
// our glyph it's stretched to the constrained size.
|
||||||
context.scaleCTM(
|
context.scaleCTM(
|
||||||
|
|
@ -534,7 +542,15 @@ pub const Face = struct {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw our glyph.
|
// Draw our glyph.
|
||||||
self.font.drawGlyphs(&glyphs, &.{.{ .x = draw_x, .y = draw_y }}, ctx);
|
//
|
||||||
|
// We offset the position by the negated bearings so that the
|
||||||
|
// glyph is drawn at exactly [0, 0], which is then offset to
|
||||||
|
// the appropriate fractional position by the translation we
|
||||||
|
// did before scaling.
|
||||||
|
self.font.drawGlyphs(&glyphs, &.{.{
|
||||||
|
.x = -rect.origin.x,
|
||||||
|
.y = -rect.origin.y,
|
||||||
|
}}, ctx);
|
||||||
|
|
||||||
// Write our rasterized glyph to the atlas.
|
// Write our rasterized glyph to the atlas.
|
||||||
const region = try atlas.reserve(alloc, px_width, px_height);
|
const region = try atlas.reserve(alloc, px_width, px_height);
|
||||||
|
|
|
||||||
|
|
@ -511,6 +511,9 @@ fn testDrawRanges(
|
||||||
}
|
}
|
||||||
|
|
||||||
test "sprite face render all sprites" {
|
test "sprite face render all sprites" {
|
||||||
|
// This test is way too slow to run under Valgrind, unfortunately.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
|
||||||
|
|
||||||
// Renders all sprites to an atlas and compares
|
// Renders all sprites to an atlas and compares
|
||||||
// it to a ground truth for regression testing.
|
// it to a ground truth for regression testing.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -589,6 +589,84 @@ pub const Key = enum(c_int) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this key should be remappable by the operating system.
|
||||||
|
///
|
||||||
|
/// On certain OSes (namely Linux and the BSDs) certain keys like the
|
||||||
|
/// functional keys are expected to be remappable by the user, such as
|
||||||
|
/// in the very common use case of swapping the Caps Lock key with the
|
||||||
|
/// Escape key with the XKB option `caps:swapescape`.
|
||||||
|
///
|
||||||
|
/// However, the way XKB implements this is by essentially acting as a
|
||||||
|
/// software key remapper that destroys all information about the original
|
||||||
|
/// physical key, leading to very annoying bugs like #7309 where the
|
||||||
|
/// physical key `XKB_KEY_c` gets remapped into `XKB_KEY_Cyrillic_tse`,
|
||||||
|
/// which causes all of our physical key handling to completely break down.
|
||||||
|
/// _Very naughty._
|
||||||
|
///
|
||||||
|
/// As a compromise, given that writing system keys (§3.1.1) comprise the
|
||||||
|
/// majority of keys that "change meaning [...] based on the current locale
|
||||||
|
/// and keyboard layout", we allow all other keys to be remapped by default
|
||||||
|
/// since they should be fairly harmless. We might consider making this
|
||||||
|
/// configurable, but for now this should at least placate most people.
|
||||||
|
pub fn shouldBeRemappable(self: Key) bool {
|
||||||
|
return switch (self) {
|
||||||
|
// "Writing System Keys" § 3.1.1
|
||||||
|
.backquote,
|
||||||
|
.backslash,
|
||||||
|
.bracket_left,
|
||||||
|
.bracket_right,
|
||||||
|
.comma,
|
||||||
|
.digit_0,
|
||||||
|
.digit_1,
|
||||||
|
.digit_2,
|
||||||
|
.digit_3,
|
||||||
|
.digit_4,
|
||||||
|
.digit_5,
|
||||||
|
.digit_6,
|
||||||
|
.digit_7,
|
||||||
|
.digit_8,
|
||||||
|
.digit_9,
|
||||||
|
.equal,
|
||||||
|
.intl_backslash,
|
||||||
|
.intl_ro,
|
||||||
|
.intl_yen,
|
||||||
|
.key_a,
|
||||||
|
.key_b,
|
||||||
|
.key_c,
|
||||||
|
.key_d,
|
||||||
|
.key_e,
|
||||||
|
.key_f,
|
||||||
|
.key_g,
|
||||||
|
.key_h,
|
||||||
|
.key_i,
|
||||||
|
.key_j,
|
||||||
|
.key_k,
|
||||||
|
.key_l,
|
||||||
|
.key_m,
|
||||||
|
.key_n,
|
||||||
|
.key_o,
|
||||||
|
.key_p,
|
||||||
|
.key_q,
|
||||||
|
.key_r,
|
||||||
|
.key_s,
|
||||||
|
.key_t,
|
||||||
|
.key_u,
|
||||||
|
.key_v,
|
||||||
|
.key_w,
|
||||||
|
.key_x,
|
||||||
|
.key_y,
|
||||||
|
.key_z,
|
||||||
|
.minus,
|
||||||
|
.period,
|
||||||
|
.quote,
|
||||||
|
.semicolon,
|
||||||
|
.slash,
|
||||||
|
=> false,
|
||||||
|
|
||||||
|
else => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if this is a keypad key.
|
/// Returns true if this is a keypad key.
|
||||||
pub fn keypad(self: Key) bool {
|
pub fn keypad(self: Key) bool {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
|
|
|
||||||
|
|
@ -172,13 +172,10 @@ pub fn init(surface: *Surface) !Inspector {
|
||||||
.surface = surface,
|
.surface = surface,
|
||||||
.key_events = key_buf,
|
.key_events = key_buf,
|
||||||
.vt_events = vt_events,
|
.vt_events = vt_events,
|
||||||
.vt_stream = .{
|
.vt_stream = stream: {
|
||||||
.handler = vt_handler,
|
var s: inspector.termio.Stream = .init(vt_handler);
|
||||||
.parser = .{
|
s.parser.osc_parser.alloc = surface.alloc;
|
||||||
.osc_parser = .{
|
break :stream s;
|
||||||
.alloc = surface.alloc,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,15 @@ fi
|
||||||
|
|
||||||
# Use try-always to have the right error code.
|
# Use try-always to have the right error code.
|
||||||
{
|
{
|
||||||
# Zsh treats empty $ZDOTDIR as if it was "/". We do the same.
|
# Zsh treats unset ZDOTDIR as if it was HOME. We do the same.
|
||||||
#
|
#
|
||||||
# Source the user's zshenv before sourcing ghostty.zsh because the former
|
# Source the user's .zshenv before sourcing ghostty-integration because the
|
||||||
# might set fpath and other things without which ghostty.zsh won't work.
|
# former might set fpath and other things without which ghostty-integration
|
||||||
|
# won't work.
|
||||||
#
|
#
|
||||||
# Use typeset in case we are in a function with warn_create_global in
|
# Use typeset in case we are in a function with warn_create_global in
|
||||||
# effect. Unlikely but better safe than sorry.
|
# effect. Unlikely but better safe than sorry.
|
||||||
'builtin' 'typeset' _ghostty_file=${ZDOTDIR-~}"/.zshenv"
|
'builtin' 'typeset' _ghostty_file=${ZDOTDIR-$HOME}"/.zshenv"
|
||||||
# Zsh ignores unreadable rc files. We do the same.
|
# Zsh ignores unreadable rc files. We do the same.
|
||||||
# Zsh ignores rc files that are directories, and so does source.
|
# Zsh ignores rc files that are directories, and so does source.
|
||||||
[[ ! -r "$_ghostty_file" ]] || 'builtin' 'source' '--' "$_ghostty_file"
|
[[ ! -r "$_ghostty_file" ]] || 'builtin' 'source' '--' "$_ghostty_file"
|
||||||
|
|
@ -45,6 +46,7 @@ fi
|
||||||
'builtin' 'autoload' '--' 'is-at-least'
|
'builtin' 'autoload' '--' 'is-at-least'
|
||||||
'is-at-least' "5.1" || {
|
'is-at-least' "5.1" || {
|
||||||
builtin echo "ZSH ${ZSH_VERSION} is too old for ghostty shell integration" > /dev/stderr
|
builtin echo "ZSH ${ZSH_VERSION} is too old for ghostty shell integration" > /dev/stderr
|
||||||
|
'builtin' 'unset' '_ghostty_file'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
# ${(%):-%x} is the path to the current file.
|
# ${(%):-%x} is the path to the current file.
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ test "OSC generator valid" {
|
||||||
};
|
};
|
||||||
for (0..50) |_| {
|
for (0..50) |_| {
|
||||||
const seq = try gen.next(&buf);
|
const seq = try gen.next(&buf);
|
||||||
var parser: terminal.osc.Parser = .{};
|
var parser: terminal.osc.Parser = .init();
|
||||||
for (seq[2 .. seq.len - 1]) |c| parser.next(c);
|
for (seq[2 .. seq.len - 1]) |c| parser.next(c);
|
||||||
try testing.expect(parser.end(null) != null);
|
try testing.expect(parser.end(null) != null);
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +214,7 @@ test "OSC generator invalid" {
|
||||||
};
|
};
|
||||||
for (0..50) |_| {
|
for (0..50) |_| {
|
||||||
const seq = try gen.next(&buf);
|
const seq = try gen.next(&buf);
|
||||||
var parser: terminal.osc.Parser = .{};
|
var parser: terminal.osc.Parser = .init();
|
||||||
for (seq[2 .. seq.len - 1]) |c| parser.next(c);
|
for (seq[2 .. seq.len - 1]) |c| parser.next(c);
|
||||||
try testing.expect(parser.end(null) == null);
|
try testing.expect(parser.end(null) == null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1004,23 +1004,34 @@ const ReflowCursor = struct {
|
||||||
// Copy the graphemes
|
// Copy the graphemes
|
||||||
const cps = src_page.lookupGrapheme(cell).?;
|
const cps = src_page.lookupGrapheme(cell).?;
|
||||||
|
|
||||||
// If our page can't support an additional cell with
|
// If our page can't support an additional cell
|
||||||
// graphemes then we create a new page for this row.
|
// with graphemes then we increase capacity.
|
||||||
if (self.page.graphemeCount() >= self.page.graphemeCapacity()) {
|
if (self.page.graphemeCount() >= self.page.graphemeCapacity()) {
|
||||||
try self.moveLastRowToNewPage(list, cap);
|
try self.adjustCapacity(list, .{
|
||||||
} else {
|
.hyperlink_bytes = cap.grapheme_bytes * 2,
|
||||||
// Attempt to allocate the space that would be required for
|
});
|
||||||
// these graphemes, and if it's not available, create a new
|
}
|
||||||
// page for this row.
|
|
||||||
if (self.page.grapheme_alloc.alloc(
|
// Attempt to allocate the space that would be required
|
||||||
u21,
|
// for these graphemes, and if it's not available, then
|
||||||
self.page.memory,
|
// increase capacity.
|
||||||
cps.len,
|
if (self.page.grapheme_alloc.alloc(
|
||||||
)) |slice| {
|
u21,
|
||||||
self.page.grapheme_alloc.free(self.page.memory, slice);
|
self.page.memory,
|
||||||
} else |_| {
|
cps.len,
|
||||||
try self.moveLastRowToNewPage(list, cap);
|
)) |slice| {
|
||||||
|
self.page.grapheme_alloc.free(self.page.memory, slice);
|
||||||
|
} else |_| {
|
||||||
|
// Grow our capacity until we can
|
||||||
|
// definitely fit the extra bytes.
|
||||||
|
const required = cps.len * @sizeOf(u21);
|
||||||
|
var new_grapheme_capacity: usize = cap.grapheme_bytes;
|
||||||
|
while (new_grapheme_capacity - cap.grapheme_bytes < required) {
|
||||||
|
new_grapheme_capacity *= 2;
|
||||||
}
|
}
|
||||||
|
try self.adjustCapacity(list, .{
|
||||||
|
.grapheme_bytes = new_grapheme_capacity,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// This shouldn't fail since we made sure we have space above.
|
// This shouldn't fail since we made sure we have space above.
|
||||||
|
|
@ -1032,25 +1043,67 @@ const ReflowCursor = struct {
|
||||||
const src_id = src_page.lookupHyperlink(cell).?;
|
const src_id = src_page.lookupHyperlink(cell).?;
|
||||||
const src_link = src_page.hyperlink_set.get(src_page.memory, src_id);
|
const src_link = src_page.hyperlink_set.get(src_page.memory, src_id);
|
||||||
|
|
||||||
// If our page can't support an additional cell with
|
// If our page can't support an additional cell
|
||||||
// a hyperlink ID then we create a new page for this row.
|
// with a hyperlink then we increase capacity.
|
||||||
if (self.page.hyperlinkCount() >= self.page.hyperlinkCapacity()) {
|
if (self.page.hyperlinkCount() >= self.page.hyperlinkCapacity()) {
|
||||||
try self.moveLastRowToNewPage(list, cap);
|
try self.adjustCapacity(list, .{
|
||||||
|
.hyperlink_bytes = cap.hyperlink_bytes * 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the string alloc has sufficient capacity
|
||||||
|
// to dupe the link (and the ID if it's not implicit).
|
||||||
|
const additional_required_string_capacity =
|
||||||
|
src_link.uri.len +
|
||||||
|
switch (src_link.id) {
|
||||||
|
.explicit => |v| v.len,
|
||||||
|
.implicit => 0,
|
||||||
|
};
|
||||||
|
if (self.page.string_alloc.alloc(
|
||||||
|
u8,
|
||||||
|
self.page.memory,
|
||||||
|
additional_required_string_capacity,
|
||||||
|
)) |slice| {
|
||||||
|
// We have enough capacity, free the test alloc.
|
||||||
|
self.page.string_alloc.free(self.page.memory, slice);
|
||||||
|
} else |_| {
|
||||||
|
// Grow our capacity until we can
|
||||||
|
// definitely fit the extra bytes.
|
||||||
|
var new_string_capacity: usize = cap.string_bytes;
|
||||||
|
while (new_string_capacity - cap.string_bytes < additional_required_string_capacity) {
|
||||||
|
new_string_capacity *= 2;
|
||||||
|
}
|
||||||
|
try self.adjustCapacity(list, .{
|
||||||
|
.string_bytes = new_string_capacity,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const dst_id = self.page.hyperlink_set.addWithIdContext(
|
const dst_id = self.page.hyperlink_set.addWithIdContext(
|
||||||
self.page.memory,
|
self.page.memory,
|
||||||
|
// We made sure there was enough capacity for this above.
|
||||||
try src_link.dupe(src_page, self.page),
|
try src_link.dupe(src_page, self.page),
|
||||||
src_id,
|
src_id,
|
||||||
.{ .page = self.page },
|
.{ .page = self.page },
|
||||||
) catch id: {
|
) catch |err| id: {
|
||||||
// We have no space for this link,
|
// If the add failed then either the set needs to grow
|
||||||
// so make a new page for this row.
|
// or it needs to be rehashed. Either one of those can
|
||||||
try self.moveLastRowToNewPage(list, cap);
|
// be accomplished by adjusting capacity, either with
|
||||||
|
// no actual change or with an increased hyperlink cap.
|
||||||
|
try self.adjustCapacity(list, switch (err) {
|
||||||
|
error.OutOfMemory => .{
|
||||||
|
.hyperlink_bytes = cap.hyperlink_bytes * 2,
|
||||||
|
},
|
||||||
|
error.NeedsRehash => .{},
|
||||||
|
});
|
||||||
|
|
||||||
break :id try self.page.hyperlink_set.addContext(
|
// We assume this one will succeed. We dupe the link
|
||||||
|
// again, and don't have to worry about the other one
|
||||||
|
// because adjusting the capacity naturally clears up
|
||||||
|
// any managed memory not associated with a cell yet.
|
||||||
|
break :id try self.page.hyperlink_set.addWithIdContext(
|
||||||
self.page.memory,
|
self.page.memory,
|
||||||
try src_link.dupe(src_page, self.page),
|
try src_link.dupe(src_page, self.page),
|
||||||
|
src_id,
|
||||||
.{ .page = self.page },
|
.{ .page = self.page },
|
||||||
);
|
);
|
||||||
} orelse src_id;
|
} orelse src_id;
|
||||||
|
|
@ -1075,14 +1128,23 @@ const ReflowCursor = struct {
|
||||||
self.page.memory,
|
self.page.memory,
|
||||||
style,
|
style,
|
||||||
cell.style_id,
|
cell.style_id,
|
||||||
) catch id: {
|
) catch |err| id: {
|
||||||
// We have no space for this style,
|
// If the add failed then either the set needs to grow
|
||||||
// so make a new page for this row.
|
// or it needs to be rehashed. Either one of those can
|
||||||
try self.moveLastRowToNewPage(list, cap);
|
// be accomplished by adjusting capacity, either with
|
||||||
|
// no actual change or with an increased style cap.
|
||||||
|
try self.adjustCapacity(list, switch (err) {
|
||||||
|
error.OutOfMemory => .{
|
||||||
|
.styles = cap.styles * 2,
|
||||||
|
},
|
||||||
|
error.NeedsRehash => .{},
|
||||||
|
});
|
||||||
|
|
||||||
break :id try self.page.styles.add(
|
// We assume this one will succeed.
|
||||||
|
break :id try self.page.styles.addWithId(
|
||||||
self.page.memory,
|
self.page.memory,
|
||||||
style,
|
style,
|
||||||
|
cell.style_id,
|
||||||
);
|
);
|
||||||
} orelse cell.style_id;
|
} orelse cell.style_id;
|
||||||
|
|
||||||
|
|
@ -1150,6 +1212,22 @@ const ReflowCursor = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adjust the capacity of the current page.
|
||||||
|
fn adjustCapacity(
|
||||||
|
self: *ReflowCursor,
|
||||||
|
list: *PageList,
|
||||||
|
adjustment: AdjustCapacity,
|
||||||
|
) !void {
|
||||||
|
const old_x = self.x;
|
||||||
|
const old_y = self.y;
|
||||||
|
|
||||||
|
self.* = .init(try list.adjustCapacity(
|
||||||
|
self.node,
|
||||||
|
adjustment,
|
||||||
|
));
|
||||||
|
self.cursorAbsolute(old_x, old_y);
|
||||||
|
}
|
||||||
|
|
||||||
/// True if this cursor is at the bottom of the page by capacity,
|
/// True if this cursor is at the bottom of the page by capacity,
|
||||||
/// i.e. we can't scroll anymore.
|
/// i.e. we can't scroll anymore.
|
||||||
fn bottom(self: *const ReflowCursor) bool {
|
fn bottom(self: *const ReflowCursor) bool {
|
||||||
|
|
@ -2317,8 +2395,8 @@ pub fn eraseRows(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.erasePage(chunk.node);
|
|
||||||
erased += chunk.node.data.size.rows;
|
erased += chunk.node.data.size.rows;
|
||||||
|
self.erasePage(chunk.node);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7029,6 +7107,7 @@ test "PageList resize reflow less cols wrap across page boundary cursor in secon
|
||||||
try testing.expect(!cells[3].hasText());
|
try testing.expect(!cells[3].hasText());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "PageList resize reflow more cols cursor in wrapped row" {
|
test "PageList resize reflow more cols cursor in wrapped row" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
@ -7222,6 +7301,296 @@ test "PageList resize reflow more cols no reflow preserves semantic prompt" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "PageList resize reflow exceeds hyperlink memory forcing capacity increase" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 2, 10, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.totalPages());
|
||||||
|
|
||||||
|
// Grow to the capacity of the first page and add
|
||||||
|
// one more row so that we have two pages total.
|
||||||
|
{
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
page.pauseIntegrityChecks(true);
|
||||||
|
for (page.size.rows..page.capacity.rows) |_| {
|
||||||
|
_ = try s.grow();
|
||||||
|
}
|
||||||
|
page.pauseIntegrityChecks(false);
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.totalPages());
|
||||||
|
try s.growRows(1);
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.totalPages());
|
||||||
|
|
||||||
|
// We now have two pages.
|
||||||
|
try std.testing.expect(s.pages.first.? != s.pages.last.?);
|
||||||
|
try std.testing.expectEqual(s.pages.last.?, s.pages.first.?.next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use almost all string alloc capacity with a hyperlink in the final
|
||||||
|
// row of the first page, and do the same on the first row of the second
|
||||||
|
// page. We also mark the row as wrapped so that when we resize with more
|
||||||
|
// cols the row unwraps and we have a single row that requires almost two
|
||||||
|
// times the base string alloc capacity.
|
||||||
|
//
|
||||||
|
// This forces the reflow to increase capacity.
|
||||||
|
//
|
||||||
|
// +--+ = PAGE 0
|
||||||
|
// : :
|
||||||
|
// | X… <- where X is hyperlinked with almost all string cap.
|
||||||
|
// +--+
|
||||||
|
// +--+ = PAGE 1
|
||||||
|
// …X | <- X here also almost hits string cap with a hyperlink.
|
||||||
|
// +--+
|
||||||
|
|
||||||
|
// Almost hit string alloc cap in bottom right of first page.
|
||||||
|
// Mark the final row as wrapped.
|
||||||
|
{
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
const id = try page.insertHyperlink(.{
|
||||||
|
.id = .{ .implicit = 0 },
|
||||||
|
.uri = "a" ** (pagepkg.string_bytes_default - 1),
|
||||||
|
});
|
||||||
|
const rac = page.getRowAndCell(page.size.cols - 1, page.size.rows - 1);
|
||||||
|
rac.row.wrap = true;
|
||||||
|
rac.cell.* = .{
|
||||||
|
.content_tag = .codepoint,
|
||||||
|
.content = .{ .codepoint = 'X' },
|
||||||
|
};
|
||||||
|
try page.setHyperlink(rac.row, rac.cell, id);
|
||||||
|
try std.testing.expectError(
|
||||||
|
error.StringsOutOfMemory,
|
||||||
|
page.insertHyperlink(.{
|
||||||
|
.id = .{ .implicit = 1 },
|
||||||
|
.uri = "AAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Almost hit string alloc cap in top left of second page.
|
||||||
|
// Mark the first row as a wrap continuation.
|
||||||
|
{
|
||||||
|
const page = &s.pages.last.?.data;
|
||||||
|
const id = try page.insertHyperlink(.{
|
||||||
|
.id = .{ .implicit = 1 },
|
||||||
|
.uri = "a" ** (pagepkg.string_bytes_default - 1),
|
||||||
|
});
|
||||||
|
const rac = page.getRowAndCell(0, 0);
|
||||||
|
rac.row.wrap_continuation = true;
|
||||||
|
rac.cell.* = .{
|
||||||
|
.content_tag = .codepoint,
|
||||||
|
.content = .{ .codepoint = 'X' },
|
||||||
|
};
|
||||||
|
try page.setHyperlink(rac.row, rac.cell, id);
|
||||||
|
try std.testing.expectError(
|
||||||
|
error.StringsOutOfMemory,
|
||||||
|
page.insertHyperlink(.{
|
||||||
|
.id = .{ .implicit = 2 },
|
||||||
|
.uri = "AAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize to 1 column wider, unwrapping the row.
|
||||||
|
try s.resize(.{ .cols = s.cols + 1, .reflow = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "PageList resize reflow exceeds grapheme memory forcing capacity increase" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 2, 10, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.totalPages());
|
||||||
|
|
||||||
|
// Grow to the capacity of the first page and add
|
||||||
|
// one more row so that we have two pages total.
|
||||||
|
{
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
page.pauseIntegrityChecks(true);
|
||||||
|
for (page.size.rows..page.capacity.rows) |_| {
|
||||||
|
_ = try s.grow();
|
||||||
|
}
|
||||||
|
page.pauseIntegrityChecks(false);
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.totalPages());
|
||||||
|
try s.growRows(1);
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.totalPages());
|
||||||
|
|
||||||
|
// We now have two pages.
|
||||||
|
try std.testing.expect(s.pages.first.? != s.pages.last.?);
|
||||||
|
try std.testing.expectEqual(s.pages.last.?, s.pages.first.?.next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use almost all grapheme alloc capacity with a grapheme in the final
|
||||||
|
// row of the first page, and do the same on the first row of the second
|
||||||
|
// page. We also mark the row as wrapped so that when we resize with more
|
||||||
|
// cols the row unwraps and we have a single row that requires almost two
|
||||||
|
// times the base grapheme alloc capacity.
|
||||||
|
//
|
||||||
|
// This forces the reflow to increase capacity.
|
||||||
|
//
|
||||||
|
// +--+ = PAGE 0
|
||||||
|
// : :
|
||||||
|
// | X… <- where X is a grapheme which uses almost all the capacity.
|
||||||
|
// +--+
|
||||||
|
// +--+ = PAGE 1
|
||||||
|
// …X | <- X here also almost hits grapheme cap.
|
||||||
|
// +--+
|
||||||
|
|
||||||
|
// Almost hit grapheme alloc cap in bottom right of first page.
|
||||||
|
// Mark the final row as wrapped.
|
||||||
|
{
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
const rac = page.getRowAndCell(page.size.cols - 1, page.size.rows - 1);
|
||||||
|
rac.row.wrap = true;
|
||||||
|
rac.cell.* = .{
|
||||||
|
.content_tag = .codepoint,
|
||||||
|
.content = .{ .codepoint = 'X' },
|
||||||
|
};
|
||||||
|
try page.setGraphemes(
|
||||||
|
rac.row,
|
||||||
|
rac.cell,
|
||||||
|
&@as(
|
||||||
|
[
|
||||||
|
@divFloor(
|
||||||
|
pagepkg.grapheme_bytes_default - 1,
|
||||||
|
@sizeOf(u21),
|
||||||
|
)
|
||||||
|
]u21,
|
||||||
|
@splat('a'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
try std.testing.expectError(
|
||||||
|
error.OutOfMemory,
|
||||||
|
page.grapheme_alloc.alloc(
|
||||||
|
u21,
|
||||||
|
page.memory,
|
||||||
|
16,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Almost hit grapheme alloc cap in top left of second page.
|
||||||
|
// Mark the first row as a wrap continuation.
|
||||||
|
{
|
||||||
|
const page = &s.pages.last.?.data;
|
||||||
|
const rac = page.getRowAndCell(0, 0);
|
||||||
|
rac.row.wrap = true;
|
||||||
|
rac.cell.* = .{
|
||||||
|
.content_tag = .codepoint,
|
||||||
|
.content = .{ .codepoint = 'X' },
|
||||||
|
};
|
||||||
|
try page.setGraphemes(
|
||||||
|
rac.row,
|
||||||
|
rac.cell,
|
||||||
|
&@as(
|
||||||
|
[
|
||||||
|
@divFloor(
|
||||||
|
pagepkg.grapheme_bytes_default - 1,
|
||||||
|
@sizeOf(u21),
|
||||||
|
)
|
||||||
|
]u21,
|
||||||
|
@splat('a'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
try std.testing.expectError(
|
||||||
|
error.OutOfMemory,
|
||||||
|
page.grapheme_alloc.alloc(
|
||||||
|
u21,
|
||||||
|
page.memory,
|
||||||
|
16,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize to 1 column wider, unwrapping the row.
|
||||||
|
try s.resize(.{ .cols = s.cols + 1, .reflow = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "PageList resize reflow exceeds style memory forcing capacity increase" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, pagepkg.std_capacity.styles - 1, 10, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.totalPages());
|
||||||
|
|
||||||
|
// Grow to the capacity of the first page and add
|
||||||
|
// one more row so that we have two pages total.
|
||||||
|
{
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
page.pauseIntegrityChecks(true);
|
||||||
|
for (page.size.rows..page.capacity.rows) |_| {
|
||||||
|
_ = try s.grow();
|
||||||
|
}
|
||||||
|
page.pauseIntegrityChecks(false);
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.totalPages());
|
||||||
|
try s.growRows(1);
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.totalPages());
|
||||||
|
|
||||||
|
// We now have two pages.
|
||||||
|
try std.testing.expect(s.pages.first.? != s.pages.last.?);
|
||||||
|
try std.testing.expectEqual(s.pages.last.?, s.pages.first.?.next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give each cell in the final row of the first page a unique style.
|
||||||
|
// Mark the final row as wrapped.
|
||||||
|
{
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
for (0..s.cols) |x| {
|
||||||
|
const id = page.styles.add(
|
||||||
|
page.memory,
|
||||||
|
.{
|
||||||
|
.bg_color = .{ .rgb = .{
|
||||||
|
.r = @truncate(x),
|
||||||
|
.g = @truncate(x >> 8),
|
||||||
|
.b = @truncate(x >> 16),
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
) catch break;
|
||||||
|
|
||||||
|
const rac = page.getRowAndCell(x, page.size.rows - 1);
|
||||||
|
rac.row.wrap = true;
|
||||||
|
rac.row.styled = true;
|
||||||
|
rac.cell.* = .{
|
||||||
|
.content_tag = .codepoint,
|
||||||
|
.content = .{ .codepoint = 'X' },
|
||||||
|
.style_id = id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the same for the first row of the second page.
|
||||||
|
// Mark the first row as a wrap continuation.
|
||||||
|
{
|
||||||
|
const page = &s.pages.last.?.data;
|
||||||
|
for (0..s.cols) |x| {
|
||||||
|
const id = page.styles.add(
|
||||||
|
page.memory,
|
||||||
|
.{
|
||||||
|
.fg_color = .{ .rgb = .{
|
||||||
|
.r = @truncate(x),
|
||||||
|
.g = @truncate(x >> 8),
|
||||||
|
.b = @truncate(x >> 16),
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
) catch break;
|
||||||
|
|
||||||
|
const rac = page.getRowAndCell(x, 0);
|
||||||
|
rac.row.wrap_continuation = true;
|
||||||
|
rac.row.styled = true;
|
||||||
|
rac.cell.* = .{
|
||||||
|
.content_tag = .codepoint,
|
||||||
|
.content = .{ .codepoint = 'X' },
|
||||||
|
.style_id = id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize to twice as wide, fully unwrapping the row.
|
||||||
|
try s.resize(.{ .cols = s.cols * 2, .reflow = true });
|
||||||
|
}
|
||||||
|
|
||||||
test "PageList resize reflow more cols unwrap wide spacer head" {
|
test "PageList resize reflow more cols unwrap wide spacer head" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
@ -7767,6 +8136,7 @@ test "PageList resize reflow less cols wrapped rows with graphemes" {
|
||||||
try testing.expectEqual(@as(u21, 'A'), cps[0]);
|
try testing.expectEqual(@as(u21, 'A'), cps[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "PageList resize reflow less cols cursor in wrapped row" {
|
test "PageList resize reflow less cols cursor in wrapped row" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
|
||||||
|
|
@ -209,24 +209,42 @@ const MAX_INTERMEDIATE = 4;
|
||||||
const MAX_PARAMS = 24;
|
const MAX_PARAMS = 24;
|
||||||
|
|
||||||
/// Current state of the state machine
|
/// Current state of the state machine
|
||||||
state: State = .ground,
|
state: State,
|
||||||
|
|
||||||
/// Intermediate tracking.
|
/// Intermediate tracking.
|
||||||
intermediates: [MAX_INTERMEDIATE]u8 = undefined,
|
intermediates: [MAX_INTERMEDIATE]u8,
|
||||||
intermediates_idx: u8 = 0,
|
intermediates_idx: u8,
|
||||||
|
|
||||||
/// Param tracking, building
|
/// Param tracking, building
|
||||||
params: [MAX_PARAMS]u16 = undefined,
|
params: [MAX_PARAMS]u16,
|
||||||
params_sep: Action.CSI.SepList = .initEmpty(),
|
params_sep: Action.CSI.SepList,
|
||||||
params_idx: u8 = 0,
|
params_idx: u8,
|
||||||
param_acc: u16 = 0,
|
param_acc: u16,
|
||||||
param_acc_idx: u8 = 0,
|
param_acc_idx: u8,
|
||||||
|
|
||||||
/// Parser for OSC sequences
|
/// Parser for OSC sequences
|
||||||
osc_parser: osc.Parser = .{},
|
osc_parser: osc.Parser,
|
||||||
|
|
||||||
pub fn init() Parser {
|
pub fn init() Parser {
|
||||||
return .{};
|
var result: Parser = .{
|
||||||
|
.state = .ground,
|
||||||
|
.intermediates_idx = 0,
|
||||||
|
.params_sep = .initEmpty(),
|
||||||
|
.params_idx = 0,
|
||||||
|
.param_acc = 0,
|
||||||
|
.param_acc_idx = 0,
|
||||||
|
.osc_parser = .init(),
|
||||||
|
|
||||||
|
.intermediates = undefined,
|
||||||
|
.params = undefined,
|
||||||
|
};
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) {
|
||||||
|
// Initialize our undefined fields so Valgrind can catch it.
|
||||||
|
// https://github.com/ziglang/zig/issues/19148
|
||||||
|
result.intermediates = undefined;
|
||||||
|
result.params = undefined;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Parser) void {
|
pub fn deinit(self: *Parser) void {
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,11 @@ pub fn deinit(self: *Screen) void {
|
||||||
/// ensure they're also calling page integrity checks if necessary.
|
/// ensure they're also calling page integrity checks if necessary.
|
||||||
pub fn assertIntegrity(self: *const Screen) void {
|
pub fn assertIntegrity(self: *const Screen) void {
|
||||||
if (build_config.slow_runtime_safety) {
|
if (build_config.slow_runtime_safety) {
|
||||||
|
// We don't run integrity checks on Valgrind because its soooooo slow,
|
||||||
|
// Valgrind is our integrity checker, and we run these during unit
|
||||||
|
// tests (non-Valgrind) anyways so we're verifying anyways.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return;
|
||||||
|
|
||||||
assert(self.cursor.x < self.pages.cols);
|
assert(self.cursor.x < self.pages.cols);
|
||||||
assert(self.cursor.y < self.pages.rows);
|
assert(self.cursor.y < self.pages.rows);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,7 @@ pub fn resize(self: *Tabstops, alloc: Allocator, cols: usize) !void {
|
||||||
|
|
||||||
// Note: we can probably try to realloc here but I'm not sure it matters.
|
// Note: we can probably try to realloc here but I'm not sure it matters.
|
||||||
const new = try alloc.alloc(Unit, size);
|
const new = try alloc.alloc(Unit, size);
|
||||||
|
@memset(new, 0);
|
||||||
if (self.dynamic_stops.len > 0) {
|
if (self.dynamic_stops.len > 0) {
|
||||||
fastmem.copy(Unit, new, self.dynamic_stops);
|
fastmem.copy(Unit, new, self.dynamic_stops);
|
||||||
alloc.free(self.dynamic_stops);
|
alloc.free(self.dynamic_stops);
|
||||||
|
|
|
||||||
|
|
@ -2824,14 +2824,21 @@ test "Terminal: input glitch text" {
|
||||||
var t = try init(alloc, .{ .cols = 30, .rows = 30 });
|
var t = try init(alloc, .{ .cols = 30, .rows = 30 });
|
||||||
defer t.deinit(alloc);
|
defer t.deinit(alloc);
|
||||||
|
|
||||||
const page = t.screen.pages.pages.first.?;
|
// Get our initial grapheme capacity.
|
||||||
const grapheme_cap = page.data.capacity.grapheme_bytes;
|
const grapheme_cap = cap: {
|
||||||
|
const page = t.screen.pages.pages.first.?;
|
||||||
|
break :cap page.data.capacity.grapheme_bytes;
|
||||||
|
};
|
||||||
|
|
||||||
while (page.data.capacity.grapheme_bytes == grapheme_cap) {
|
// Print glitch text until our capacity changes
|
||||||
|
while (true) {
|
||||||
|
const page = t.screen.pages.pages.first.?;
|
||||||
|
if (page.data.capacity.grapheme_bytes != grapheme_cap) break;
|
||||||
try t.printString(glitch);
|
try t.printString(glitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're testing to make sure that grapheme capacity gets increased.
|
// We're testing to make sure that grapheme capacity gets increased.
|
||||||
|
const page = t.screen.pages.pages.first.?;
|
||||||
try testing.expect(page.data.capacity.grapheme_bytes > grapheme_cap);
|
try testing.expect(page.data.capacity.grapheme_bytes > grapheme_cap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,28 +108,59 @@ pub fn BitmapAllocator(comptime chunk_size: comptime_int) type {
|
||||||
const chunks = self.chunks.ptr(base);
|
const chunks = self.chunks.ptr(base);
|
||||||
const chunk_idx = @divExact(@intFromPtr(slice.ptr) - @intFromPtr(chunks), chunk_size);
|
const chunk_idx = @divExact(@intFromPtr(slice.ptr) - @intFromPtr(chunks), chunk_size);
|
||||||
|
|
||||||
// From the chunk index, we can find the starting bitmap index
|
|
||||||
// and the bit within the last bitmap.
|
|
||||||
var bitmap_idx = @divFloor(chunk_idx, 64);
|
|
||||||
const bitmap_bit = chunk_idx % 64;
|
|
||||||
const bitmaps = self.bitmap.ptr(base);
|
const bitmaps = self.bitmap.ptr(base);
|
||||||
|
|
||||||
// If our chunk count is over 64 then we need to handle the
|
// Current bitmap index.
|
||||||
// case where we have to mark multiple bitmaps.
|
var i: usize = @divFloor(chunk_idx, 64);
|
||||||
if (chunk_count > 64) {
|
// Number of chunks we still have to mark as free.
|
||||||
const bitmaps_full = @divFloor(chunk_count, 64);
|
var rem: usize = chunk_count;
|
||||||
for (0..bitmaps_full) |i| bitmaps[bitmap_idx + i] = std.math.maxInt(u64);
|
|
||||||
bitmap_idx += bitmaps_full;
|
// Mark any bits in the starting bitmap that need to be marked.
|
||||||
|
{
|
||||||
|
// Bit index.
|
||||||
|
const bit = chunk_idx % 64;
|
||||||
|
// Number of bits we need to mark in this bitmap.
|
||||||
|
const bits = @min(rem, 64 - bit);
|
||||||
|
|
||||||
|
bitmaps[i] |= ~@as(u64, 0) >> @intCast(64 - bits) << @intCast(bit);
|
||||||
|
rem -= bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the bitmap to mark the chunks as free. Note we have to
|
// Mark any full bitmaps worth of bits that need to be marked.
|
||||||
// do chunk_count % 64 to handle the case where our chunk count
|
i += 1;
|
||||||
// is using multiple bitmaps.
|
while (rem > 64) : (i += 1) {
|
||||||
const bitmap = &bitmaps[bitmap_idx];
|
bitmaps[i] = std.math.maxInt(u64);
|
||||||
for (0..chunk_count % 64) |i| {
|
rem -= 64;
|
||||||
const mask = @as(u64, 1) << @intCast(bitmap_bit + i);
|
|
||||||
bitmap.* |= mask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark any bits at the start of this last bitmap if it needs it.
|
||||||
|
if (rem > 0) {
|
||||||
|
bitmaps[i] |= ~@as(u64, 0) >> @intCast(64 - rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For testing only.
|
||||||
|
fn isAllocated(self: *Self, base: anytype, slice: anytype) bool {
|
||||||
|
comptime assert(@import("builtin").is_test);
|
||||||
|
|
||||||
|
const bytes = std.mem.sliceAsBytes(slice);
|
||||||
|
const aligned_len = std.mem.alignForward(usize, bytes.len, chunk_size);
|
||||||
|
const chunk_count = @divExact(aligned_len, chunk_size);
|
||||||
|
|
||||||
|
const chunks = self.chunks.ptr(base);
|
||||||
|
const chunk_idx = @divExact(@intFromPtr(slice.ptr) - @intFromPtr(chunks), chunk_size);
|
||||||
|
|
||||||
|
const bitmaps = self.bitmap.ptr(base);
|
||||||
|
|
||||||
|
for (chunk_idx..chunk_idx + chunk_count) |i| {
|
||||||
|
const bitmap = @divFloor(i, bitmap_bit_size);
|
||||||
|
const bit = i % bitmap_bit_size;
|
||||||
|
if (bitmaps[bitmap] & (@as(u64, 1) << @intCast(bit)) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For debugging
|
/// For debugging
|
||||||
|
|
@ -188,50 +219,56 @@ fn findFreeChunks(bitmaps: []u64, n: usize) ?usize {
|
||||||
// I'm not a bit twiddling expert. Perhaps even SIMD could be used here
|
// I'm not a bit twiddling expert. Perhaps even SIMD could be used here
|
||||||
// but unsure. Contributor friendly: let's benchmark and improve this!
|
// but unsure. Contributor friendly: let's benchmark and improve this!
|
||||||
|
|
||||||
// Large chunks require special handling. In this case we look for
|
// Large chunks require special handling.
|
||||||
// divFloor sequential chunks that are maxInt, then look for the mod
|
|
||||||
// normally in the next bitmap.
|
|
||||||
if (n > @bitSizeOf(u64)) {
|
if (n > @bitSizeOf(u64)) {
|
||||||
const div = @divFloor(n, @bitSizeOf(u64));
|
var i: usize = 0;
|
||||||
const mod = n % @bitSizeOf(u64);
|
search: while (i < bitmaps.len) {
|
||||||
var seq: usize = 0;
|
// Number of chunks available at the end of this bitmap.
|
||||||
for (bitmaps, 0..) |*bitmap, idx| {
|
const prefix = @clz(~bitmaps[i]);
|
||||||
// If we aren't fully empty then reset the sequence
|
|
||||||
if (bitmap.* != std.math.maxInt(u64)) {
|
// If there are no chunks available at the end of this bitmap
|
||||||
seq = 0;
|
// then we can't start in it, so we'll try the next one.
|
||||||
|
if (prefix == 0) {
|
||||||
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we haven't reached the sequence count we're looking for
|
// Starting position if we manage to find the span we need here.
|
||||||
// then add one and continue, we're still accumulating blanks.
|
const start_bitmap = i;
|
||||||
if (seq != div) {
|
const start_bit = 64 - prefix;
|
||||||
seq += 1;
|
|
||||||
if (seq != div or mod > 0) continue;
|
// The remaining number of sequential free chunks we need to find.
|
||||||
|
var rem: usize = n - prefix;
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
while (rem > 64) : (i += 1) {
|
||||||
|
// We ran out of bitmaps, there's no sufficiently large gap.
|
||||||
|
if (i >= bitmaps.len) return null;
|
||||||
|
|
||||||
|
// There's more than 64 remaining chunks and this bitmap has
|
||||||
|
// content in it, so we try starting again with this bitmap.
|
||||||
|
if (bitmaps[i] != std.math.maxInt(u64)) continue :search;
|
||||||
|
|
||||||
|
// This bitmap is completely free, we can subtract 64 from
|
||||||
|
// our remaining number.
|
||||||
|
rem -= 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've reached the seq count see if this has mod starting empty
|
// If the number of available chunks at the start of this bitmap
|
||||||
// blanks.
|
// is less than the remaining required, we have to try again.
|
||||||
if (mod > 0) {
|
if (@ctz(~bitmaps[i]) < rem) continue;
|
||||||
const final = @as(u64, std.math.maxInt(u64)) >> @intCast(64 - mod);
|
|
||||||
if (bitmap.* & final == 0) {
|
|
||||||
// No blanks, reset.
|
|
||||||
seq = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap.* ^= final;
|
const suffix = (n - prefix) % 64;
|
||||||
|
|
||||||
|
// Found! Mark everything between our start and end as full.
|
||||||
|
bitmaps[start_bitmap] ^= ~@as(u64, 0) >> @intCast(start_bit) << @intCast(start_bit);
|
||||||
|
const full_bitmaps = @divFloor(n - prefix - suffix, 64);
|
||||||
|
for (bitmaps[start_bitmap + 1 ..][0..full_bitmaps]) |*bitmap| {
|
||||||
|
bitmap.* = 0;
|
||||||
}
|
}
|
||||||
|
if (suffix > 0) bitmaps[i] ^= ~@as(u64, 0) >> @intCast(64 - suffix);
|
||||||
|
|
||||||
// Found! Set all in our sequence to full and mask our final.
|
return start_bitmap * 64 + start_bit;
|
||||||
// The "zero_mod" modifier below handles the case where we have
|
|
||||||
// a perfectly divisible number of chunks so we don't have to
|
|
||||||
// mark the trailing bitmap.
|
|
||||||
const zero_mod = @intFromBool(mod == 0);
|
|
||||||
const start_idx = idx - (seq - zero_mod);
|
|
||||||
const end_idx = idx + zero_mod;
|
|
||||||
for (start_idx..end_idx) |i| bitmaps[i] = 0;
|
|
||||||
|
|
||||||
return (start_idx * 64);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -349,18 +386,18 @@ test "findFreeChunks larger than 64 chunks not at beginning" {
|
||||||
};
|
};
|
||||||
const idx = findFreeChunks(&bitmaps, 65).?;
|
const idx = findFreeChunks(&bitmaps, 65).?;
|
||||||
try testing.expectEqual(
|
try testing.expectEqual(
|
||||||
0b11111111_00000000_00000000_00000000_00000000_00000000_00000000_00000000,
|
0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000,
|
||||||
bitmaps[0],
|
bitmaps[0],
|
||||||
);
|
);
|
||||||
try testing.expectEqual(
|
try testing.expectEqual(
|
||||||
0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000,
|
0b11111110_00000000_00000000_00000000_00000000_00000000_00000000_00000000,
|
||||||
bitmaps[1],
|
bitmaps[1],
|
||||||
);
|
);
|
||||||
try testing.expectEqual(
|
try testing.expectEqual(
|
||||||
0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111110,
|
0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111,
|
||||||
bitmaps[2],
|
bitmaps[2],
|
||||||
);
|
);
|
||||||
try testing.expectEqual(@as(usize, 64), idx);
|
try testing.expectEqual(@as(usize, 56), idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "findFreeChunks larger than 64 chunks exact" {
|
test "findFreeChunks larger than 64 chunks exact" {
|
||||||
|
|
@ -483,3 +520,438 @@ test "BitmapAllocator alloc large" {
|
||||||
ptr[0] = 'A';
|
ptr[0] = 'A';
|
||||||
bm.free(buf, ptr);
|
bm.free(buf, ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "BitmapAllocator alloc and free one bitmap" {
|
||||||
|
const Alloc = BitmapAllocator(1);
|
||||||
|
// Capacity such that we'll have 3 bitmaps.
|
||||||
|
const cap = Alloc.bitmap_bit_size * 3;
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const layout = Alloc.layout(cap);
|
||||||
|
const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var bm = Alloc.init(.init(buf), layout);
|
||||||
|
|
||||||
|
// Allocate exactly one bitmap worth of bytes.
|
||||||
|
const slice = try bm.alloc(u8, buf, Alloc.bitmap_bit_size);
|
||||||
|
try testing.expectEqual(Alloc.bitmap_bit_size, slice.len);
|
||||||
|
|
||||||
|
@memset(slice, 0x11);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([Alloc.bitmap_bit_size]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Free it
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice));
|
||||||
|
bm.free(buf, slice);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice));
|
||||||
|
|
||||||
|
// All of our bitmaps should be free.
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u64,
|
||||||
|
&@as([3]u64, @splat(~@as(u64, 0))),
|
||||||
|
bm.bitmap.ptr(buf)[0..3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BitmapAllocator alloc and free half bitmap" {
|
||||||
|
const Alloc = BitmapAllocator(1);
|
||||||
|
// Capacity such that we'll have 3 bitmaps.
|
||||||
|
const cap = Alloc.bitmap_bit_size * 3;
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const layout = Alloc.layout(cap);
|
||||||
|
const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var bm = Alloc.init(.init(buf), layout);
|
||||||
|
|
||||||
|
// Allocate exactly half a bitmap worth of bytes.
|
||||||
|
const slice = try bm.alloc(u8, buf, Alloc.bitmap_bit_size / 2);
|
||||||
|
try testing.expectEqual(Alloc.bitmap_bit_size / 2, slice.len);
|
||||||
|
|
||||||
|
@memset(slice, 0x11);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([Alloc.bitmap_bit_size / 2]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Free it
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice));
|
||||||
|
bm.free(buf, slice);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice));
|
||||||
|
|
||||||
|
// All of our bitmaps should be free.
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u64,
|
||||||
|
&@as([3]u64, @splat(~@as(u64, 0))),
|
||||||
|
bm.bitmap.ptr(buf)[0..3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BitmapAllocator alloc and free two half bitmaps" {
|
||||||
|
const Alloc = BitmapAllocator(1);
|
||||||
|
// Capacity such that we'll have 3 bitmaps.
|
||||||
|
const cap = Alloc.bitmap_bit_size * 3;
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const layout = Alloc.layout(cap);
|
||||||
|
const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var bm = Alloc.init(.init(buf), layout);
|
||||||
|
|
||||||
|
// Allocate exactly one bitmap worth of bytes across two allocations.
|
||||||
|
const slice = try bm.alloc(u8, buf, Alloc.bitmap_bit_size / 2);
|
||||||
|
try testing.expectEqual(Alloc.bitmap_bit_size / 2, slice.len);
|
||||||
|
|
||||||
|
@memset(slice, 0x11);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([Alloc.bitmap_bit_size / 2]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
const slice2 = try bm.alloc(u8, buf, Alloc.bitmap_bit_size / 2);
|
||||||
|
try testing.expectEqual(Alloc.bitmap_bit_size / 2, slice2.len);
|
||||||
|
|
||||||
|
@memset(slice2, 0x22);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([Alloc.bitmap_bit_size / 2]u8, @splat(0x22)),
|
||||||
|
slice2,
|
||||||
|
);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([Alloc.bitmap_bit_size / 2]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Free them
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice2));
|
||||||
|
bm.free(buf, slice2);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice2));
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice));
|
||||||
|
bm.free(buf, slice);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice));
|
||||||
|
|
||||||
|
// All of our bitmaps should be free.
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u64,
|
||||||
|
&@as([3]u64, @splat(~@as(u64, 0))),
|
||||||
|
bm.bitmap.ptr(buf)[0..3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BitmapAllocator alloc and free 1.5 bitmaps" {
|
||||||
|
const Alloc = BitmapAllocator(1);
|
||||||
|
// Capacity such that we'll have 3 bitmaps.
|
||||||
|
const cap = Alloc.bitmap_bit_size * 3;
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const layout = Alloc.layout(cap);
|
||||||
|
const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var bm = Alloc.init(.init(buf), layout);
|
||||||
|
|
||||||
|
// Allocate exactly 1.5 bitmaps worth of bytes.
|
||||||
|
const slice = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 2);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 2, slice.len);
|
||||||
|
|
||||||
|
@memset(slice, 0x11);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 2]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Free them
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice));
|
||||||
|
bm.free(buf, slice);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice));
|
||||||
|
|
||||||
|
// All of our bitmaps should be free.
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u64,
|
||||||
|
&@as([3]u64, @splat(~@as(u64, 0))),
|
||||||
|
bm.bitmap.ptr(buf)[0..3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BitmapAllocator alloc and free two 1.5 bitmaps" {
|
||||||
|
const Alloc = BitmapAllocator(1);
|
||||||
|
// Capacity such that we'll have 3 bitmaps.
|
||||||
|
const cap = Alloc.bitmap_bit_size * 3;
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const layout = Alloc.layout(cap);
|
||||||
|
const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var bm = Alloc.init(.init(buf), layout);
|
||||||
|
|
||||||
|
// Allocate exactly 3 bitmaps worth of bytes across two allocations.
|
||||||
|
const slice = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 2);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 2, slice.len);
|
||||||
|
|
||||||
|
@memset(slice, 0x11);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 2]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
const slice2 = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 2);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 2, slice2.len);
|
||||||
|
|
||||||
|
@memset(slice2, 0x22);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 2]u8, @splat(0x22)),
|
||||||
|
slice2,
|
||||||
|
);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 2]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Free them
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice2));
|
||||||
|
bm.free(buf, slice2);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice2));
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice));
|
||||||
|
bm.free(buf, slice);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice));
|
||||||
|
|
||||||
|
// All of our bitmaps should be free.
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u64,
|
||||||
|
&@as([3]u64, @splat(~@as(u64, 0))),
|
||||||
|
bm.bitmap.ptr(buf)[0..3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BitmapAllocator alloc and free 1.5 bitmaps offset by 0.75" {
|
||||||
|
const Alloc = BitmapAllocator(1);
|
||||||
|
// Capacity such that we'll have 3 bitmaps.
|
||||||
|
const cap = Alloc.bitmap_bit_size * 3;
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const layout = Alloc.layout(cap);
|
||||||
|
const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var bm = Alloc.init(.init(buf), layout);
|
||||||
|
|
||||||
|
// Allocate three quarters of a bitmap first.
|
||||||
|
const slice = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 4);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 4, slice.len);
|
||||||
|
|
||||||
|
@memset(slice, 0x11);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then a 1.5 bitmap sized allocation, so that it spans
|
||||||
|
// from 0.75 to 2.25, occupying bits in 3 different bitmaps.
|
||||||
|
const slice2 = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 2);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 2, slice2.len);
|
||||||
|
|
||||||
|
@memset(slice2, 0x22);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 2]u8, @splat(0x22)),
|
||||||
|
slice2,
|
||||||
|
);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Free them
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice2));
|
||||||
|
bm.free(buf, slice2);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice2));
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice));
|
||||||
|
bm.free(buf, slice);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice));
|
||||||
|
|
||||||
|
// All of our bitmaps should be free.
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u64,
|
||||||
|
&@as([3]u64, @splat(~@as(u64, 0))),
|
||||||
|
bm.bitmap.ptr(buf)[0..3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BitmapAllocator alloc and free three 0.75 bitmaps" {
|
||||||
|
const Alloc = BitmapAllocator(1);
|
||||||
|
// Capacity such that we'll have 3 bitmaps.
|
||||||
|
const cap = Alloc.bitmap_bit_size * 3;
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const layout = Alloc.layout(cap);
|
||||||
|
const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var bm = Alloc.init(.init(buf), layout);
|
||||||
|
|
||||||
|
// Allocate three quarters of a bitmap three times.
|
||||||
|
const slice = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 4);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 4, slice.len);
|
||||||
|
|
||||||
|
@memset(slice, 0x11);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
const slice2 = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 4);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 4, slice2.len);
|
||||||
|
|
||||||
|
@memset(slice2, 0x22);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x22)),
|
||||||
|
slice2,
|
||||||
|
);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
const slice3 = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 4);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 4, slice3.len);
|
||||||
|
|
||||||
|
@memset(slice3, 0x33);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x33)),
|
||||||
|
slice3,
|
||||||
|
);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x22)),
|
||||||
|
slice2,
|
||||||
|
);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Free them
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice2));
|
||||||
|
bm.free(buf, slice2);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice2));
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice));
|
||||||
|
bm.free(buf, slice);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice));
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice3));
|
||||||
|
bm.free(buf, slice3);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice3));
|
||||||
|
|
||||||
|
// All of our bitmaps should be free.
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u64,
|
||||||
|
&@as([3]u64, @splat(~@as(u64, 0))),
|
||||||
|
bm.bitmap.ptr(buf)[0..3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BitmapAllocator alloc and free two 1.5 bitmaps offset 0.75" {
|
||||||
|
const Alloc = BitmapAllocator(1);
|
||||||
|
// Capacity such that we'll have 4 bitmaps.
|
||||||
|
const cap = Alloc.bitmap_bit_size * 4;
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const layout = Alloc.layout(cap);
|
||||||
|
const buf = try alloc.alignedAlloc(u8, Alloc.base_align, layout.total_size);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var bm = Alloc.init(.init(buf), layout);
|
||||||
|
|
||||||
|
// First allocate a 0.75 bitmap
|
||||||
|
const slice = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 4);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 4, slice.len);
|
||||||
|
|
||||||
|
@memset(slice, 0x11);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then two 1.5 bitmaps
|
||||||
|
const slice2 = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 2);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 2, slice2.len);
|
||||||
|
|
||||||
|
@memset(slice2, 0x22);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 2]u8, @splat(0x22)),
|
||||||
|
slice2,
|
||||||
|
);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
const slice3 = try bm.alloc(u8, buf, 3 * Alloc.bitmap_bit_size / 2);
|
||||||
|
try testing.expectEqual(3 * Alloc.bitmap_bit_size / 2, slice3.len);
|
||||||
|
|
||||||
|
@memset(slice3, 0x33);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 2]u8, @splat(0x33)),
|
||||||
|
slice3,
|
||||||
|
);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 2]u8, @splat(0x22)),
|
||||||
|
slice2,
|
||||||
|
);
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u8,
|
||||||
|
&@as([3 * Alloc.bitmap_bit_size / 4]u8, @splat(0x11)),
|
||||||
|
slice,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Free them
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice2));
|
||||||
|
bm.free(buf, slice2);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice2));
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice));
|
||||||
|
bm.free(buf, slice);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice));
|
||||||
|
try testing.expect(bm.isAllocated(buf, slice3));
|
||||||
|
bm.free(buf, slice3);
|
||||||
|
try testing.expect(!bm.isAllocated(buf, slice3));
|
||||||
|
|
||||||
|
// All of our bitmaps should be free.
|
||||||
|
try testing.expectEqualSlices(
|
||||||
|
u64,
|
||||||
|
&@as([4]u64, @splat(~@as(u64, 0))),
|
||||||
|
bm.bitmap.ptr(buf)[0..4],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,25 @@ pub const PageEntry = struct {
|
||||||
other.uri.offset.ptr(other_base)[0..other.uri.len],
|
other.uri.offset.ptr(other_base)[0..other.uri.len],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Free the memory for this entry from its page.
|
||||||
|
pub fn free(
|
||||||
|
self: *const PageEntry,
|
||||||
|
page: *Page,
|
||||||
|
) void {
|
||||||
|
const alloc = &page.string_alloc;
|
||||||
|
switch (self.id) {
|
||||||
|
.implicit => {},
|
||||||
|
.explicit => |v| alloc.free(
|
||||||
|
page.memory,
|
||||||
|
v.offset.ptr(page.memory)[0..v.len],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
alloc.free(
|
||||||
|
page.memory,
|
||||||
|
self.uri.offset.ptr(page.memory)[0..self.uri.len],
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The set of hyperlinks. This is ref-counted so that a set of cells
|
/// The set of hyperlinks. This is ref-counted so that a set of cells
|
||||||
|
|
@ -215,19 +234,7 @@ pub const Set = RefCountedSet(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deleted(self: *const @This(), link: PageEntry) void {
|
pub fn deleted(self: *const @This(), link: PageEntry) void {
|
||||||
const page = self.page.?;
|
link.free(self.page.?);
|
||||||
const alloc = &page.string_alloc;
|
|
||||||
switch (link.id) {
|
|
||||||
.implicit => {},
|
|
||||||
.explicit => |v| alloc.free(
|
|
||||||
page.memory,
|
|
||||||
v.offset.ptr(page.memory)[0..v.len],
|
|
||||||
),
|
|
||||||
}
|
|
||||||
alloc.free(
|
|
||||||
page.memory,
|
|
||||||
link.uri.offset.ptr(page.memory)[0..link.uri.len],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ pub const Parser = struct {
|
||||||
arena: ArenaAllocator,
|
arena: ArenaAllocator,
|
||||||
|
|
||||||
/// This is the list of KV pairs that we're building up.
|
/// This is the list of KV pairs that we're building up.
|
||||||
kv: KV = .{},
|
kv: KV,
|
||||||
|
|
||||||
/// This is used as a buffer to store the key/value of a KV pair. The value
|
/// This is used as a buffer to store the key/value of a KV pair. The value
|
||||||
/// of a KV pair is at most a 32-bit integer which at most is 10 characters
|
/// of a KV pair is at most a 32-bit integer which at most is 10 characters
|
||||||
/// (4294967295), plus one character for the sign bit on signed ints.
|
/// (4294967295), plus one character for the sign bit on signed ints.
|
||||||
kv_temp: [11]u8 = undefined,
|
kv_temp: [11]u8,
|
||||||
kv_temp_len: u4 = 0,
|
kv_temp_len: u4,
|
||||||
kv_current: u8 = 0, // Current kv key
|
kv_current: u8, // Current kv key
|
||||||
|
|
||||||
/// This is the list we use to collect the bytes from the data payload.
|
/// This is the list we use to collect the bytes from the data payload.
|
||||||
/// The Kitty Graphics protocol specification seems to imply that the
|
/// The Kitty Graphics protocol specification seems to imply that the
|
||||||
|
|
@ -38,7 +38,7 @@ pub const Parser = struct {
|
||||||
data: std.ArrayList(u8),
|
data: std.ArrayList(u8),
|
||||||
|
|
||||||
/// Internal state for parsing.
|
/// Internal state for parsing.
|
||||||
state: State = .control_key,
|
state: State,
|
||||||
|
|
||||||
const State = enum {
|
const State = enum {
|
||||||
/// Parsing k/v pairs. The "ignore" variants are in that state
|
/// Parsing k/v pairs. The "ignore" variants are in that state
|
||||||
|
|
@ -57,10 +57,22 @@ pub const Parser = struct {
|
||||||
pub fn init(alloc: Allocator) Parser {
|
pub fn init(alloc: Allocator) Parser {
|
||||||
var arena = ArenaAllocator.init(alloc);
|
var arena = ArenaAllocator.init(alloc);
|
||||||
errdefer arena.deinit();
|
errdefer arena.deinit();
|
||||||
return .{
|
var result: Parser = .{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.data = std.ArrayList(u8).init(alloc),
|
.data = std.ArrayList(u8).init(alloc),
|
||||||
|
.kv = .{},
|
||||||
|
.kv_temp_len = 0,
|
||||||
|
.kv_current = 0,
|
||||||
|
.state = .control_key,
|
||||||
|
|
||||||
|
.kv_temp = undefined,
|
||||||
};
|
};
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) {
|
||||||
|
// Initialize our undefined fields so Valgrind can catch it.
|
||||||
|
// https://github.com/ziglang/zig/issues/19148
|
||||||
|
result.kv_temp = undefined;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Parser) void {
|
pub fn deinit(self: *Parser) void {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ const kitty = @import("kitty.zig");
|
||||||
const log = std.log.scoped(.osc);
|
const log = std.log.scoped(.osc);
|
||||||
|
|
||||||
pub const Command = union(enum) {
|
pub const Command = union(enum) {
|
||||||
|
/// This generally shouldn't ever be set except as an initial zero value.
|
||||||
|
/// Ignore it.
|
||||||
|
invalid,
|
||||||
|
|
||||||
/// Set the window title of the terminal
|
/// Set the window title of the terminal
|
||||||
///
|
///
|
||||||
/// If title mode 0 is set text is expect to be hex encoded (i.e. utf-8
|
/// If title mode 0 is set text is expect to be hex encoded (i.e. utf-8
|
||||||
|
|
@ -282,23 +286,23 @@ pub const Parser = struct {
|
||||||
/// Optional allocator used to accept data longer than MAX_BUF.
|
/// Optional allocator used to accept data longer than MAX_BUF.
|
||||||
/// This only applies to some commands (e.g. OSC 52) that can
|
/// This only applies to some commands (e.g. OSC 52) that can
|
||||||
/// reasonably exceed MAX_BUF.
|
/// reasonably exceed MAX_BUF.
|
||||||
alloc: ?Allocator = null,
|
alloc: ?Allocator,
|
||||||
|
|
||||||
/// Current state of the parser.
|
/// Current state of the parser.
|
||||||
state: State = .empty,
|
state: State,
|
||||||
|
|
||||||
/// Current command of the parser, this accumulates.
|
/// Current command of the parser, this accumulates.
|
||||||
command: Command = undefined,
|
command: Command,
|
||||||
|
|
||||||
/// Buffer that stores the input we see for a single OSC command.
|
/// Buffer that stores the input we see for a single OSC command.
|
||||||
/// Slices in Command are offsets into this buffer.
|
/// Slices in Command are offsets into this buffer.
|
||||||
buf: [MAX_BUF]u8 = undefined,
|
buf: [MAX_BUF]u8,
|
||||||
buf_start: usize = 0,
|
buf_start: usize,
|
||||||
buf_idx: usize = 0,
|
buf_idx: usize,
|
||||||
buf_dynamic: ?*std.ArrayListUnmanaged(u8) = null,
|
buf_dynamic: ?*std.ArrayListUnmanaged(u8),
|
||||||
|
|
||||||
/// True when a command is complete/valid to return.
|
/// True when a command is complete/valid to return.
|
||||||
complete: bool = false,
|
complete: bool,
|
||||||
|
|
||||||
/// Temporary state that is dependent on the current state.
|
/// Temporary state that is dependent on the current state.
|
||||||
temp_state: union {
|
temp_state: union {
|
||||||
|
|
@ -310,7 +314,7 @@ pub const Parser = struct {
|
||||||
|
|
||||||
/// Temporary state for key/value pairs
|
/// Temporary state for key/value pairs
|
||||||
key: []const u8,
|
key: []const u8,
|
||||||
} = undefined,
|
},
|
||||||
|
|
||||||
// Maximum length of a single OSC command. This is the full OSC command
|
// Maximum length of a single OSC command. This is the full OSC command
|
||||||
// sequence length (excluding ESC ]). This is arbitrary, I couldn't find
|
// sequence length (excluding ESC ]). This is arbitrary, I couldn't find
|
||||||
|
|
@ -429,6 +433,37 @@ pub const Parser = struct {
|
||||||
conemu_progress_value,
|
conemu_progress_value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn init() Parser {
|
||||||
|
var result: Parser = .{
|
||||||
|
.alloc = null,
|
||||||
|
.state = .empty,
|
||||||
|
.command = .invalid,
|
||||||
|
.buf_start = 0,
|
||||||
|
.buf_idx = 0,
|
||||||
|
.buf_dynamic = null,
|
||||||
|
.complete = false,
|
||||||
|
|
||||||
|
// Keeping all our undefined values together so we can
|
||||||
|
// visually easily duplicate them in the Valgrind check below.
|
||||||
|
.buf = undefined,
|
||||||
|
.temp_state = undefined,
|
||||||
|
};
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) {
|
||||||
|
// Initialize our undefined fields so Valgrind can catch it.
|
||||||
|
// https://github.com/ziglang/zig/issues/19148
|
||||||
|
result.buf = undefined;
|
||||||
|
result.temp_state = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initAlloc(alloc: Allocator) Parser {
|
||||||
|
var result: Parser = .init();
|
||||||
|
result.alloc = alloc;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// This must be called to clean up any allocated memory.
|
/// This must be called to clean up any allocated memory.
|
||||||
pub fn deinit(self: *Parser) void {
|
pub fn deinit(self: *Parser) void {
|
||||||
self.reset();
|
self.reset();
|
||||||
|
|
@ -446,9 +481,17 @@ pub const Parser = struct {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some commands have their own memory management we need to clear.
|
||||||
|
switch (self.command) {
|
||||||
|
.kitty_color_protocol => |*v| v.list.deinit(),
|
||||||
|
.color_operation => |*v| v.operations.deinit(self.alloc.?),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
self.state = .empty;
|
self.state = .empty;
|
||||||
self.buf_start = 0;
|
self.buf_start = 0;
|
||||||
self.buf_idx = 0;
|
self.buf_idx = 0;
|
||||||
|
self.command = .invalid;
|
||||||
self.complete = false;
|
self.complete = false;
|
||||||
if (self.buf_dynamic) |ptr| {
|
if (self.buf_dynamic) |ptr| {
|
||||||
const alloc = self.alloc.?;
|
const alloc = self.alloc.?;
|
||||||
|
|
@ -456,22 +499,6 @@ pub const Parser = struct {
|
||||||
alloc.destroy(ptr);
|
alloc.destroy(ptr);
|
||||||
self.buf_dynamic = null;
|
self.buf_dynamic = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some commands have their own memory management we need to clear.
|
|
||||||
// After cleaning up these command, we reset the command to
|
|
||||||
// some nonsense (but valid) command so we don't double free.
|
|
||||||
const default: Command = .{ .hyperlink_end = {} };
|
|
||||||
switch (self.command) {
|
|
||||||
.kitty_color_protocol => |*v| {
|
|
||||||
v.list.deinit();
|
|
||||||
self.command = default;
|
|
||||||
},
|
|
||||||
.color_operation => |*v| {
|
|
||||||
v.operations.deinit(self.alloc.?);
|
|
||||||
self.command = default;
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume the next character c and advance the parser state.
|
/// Consume the next character c and advance the parser state.
|
||||||
|
|
@ -1590,7 +1617,7 @@ pub const Parser = struct {
|
||||||
test "OSC: change_window_title" {
|
test "OSC: change_window_title" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
p.next('0');
|
p.next('0');
|
||||||
p.next(';');
|
p.next(';');
|
||||||
p.next('a');
|
p.next('a');
|
||||||
|
|
@ -1603,7 +1630,7 @@ test "OSC: change_window_title" {
|
||||||
test "OSC: change_window_title with 2" {
|
test "OSC: change_window_title with 2" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
p.next('2');
|
p.next('2');
|
||||||
p.next(';');
|
p.next(';');
|
||||||
p.next('a');
|
p.next('a');
|
||||||
|
|
@ -1616,7 +1643,7 @@ test "OSC: change_window_title with 2" {
|
||||||
test "OSC: change_window_title with utf8" {
|
test "OSC: change_window_title with utf8" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
p.next('2');
|
p.next('2');
|
||||||
p.next(';');
|
p.next(';');
|
||||||
// '—' EM DASH U+2014 (E2 80 94)
|
// '—' EM DASH U+2014 (E2 80 94)
|
||||||
|
|
@ -1638,7 +1665,7 @@ test "OSC: change_window_title with utf8" {
|
||||||
test "OSC: change_window_title empty" {
|
test "OSC: change_window_title empty" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
p.next('2');
|
p.next('2');
|
||||||
p.next(';');
|
p.next(';');
|
||||||
const cmd = p.end(null).?;
|
const cmd = p.end(null).?;
|
||||||
|
|
@ -1649,7 +1676,7 @@ test "OSC: change_window_title empty" {
|
||||||
test "OSC: change_window_icon" {
|
test "OSC: change_window_icon" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
p.next('1');
|
p.next('1');
|
||||||
p.next(';');
|
p.next(';');
|
||||||
p.next('a');
|
p.next('a');
|
||||||
|
|
@ -1662,7 +1689,7 @@ test "OSC: change_window_icon" {
|
||||||
test "OSC: prompt_start" {
|
test "OSC: prompt_start" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;A";
|
const input = "133;A";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1676,7 +1703,7 @@ test "OSC: prompt_start" {
|
||||||
test "OSC: prompt_start with single option" {
|
test "OSC: prompt_start with single option" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;A;aid=14";
|
const input = "133;A;aid=14";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1689,7 +1716,7 @@ test "OSC: prompt_start with single option" {
|
||||||
test "OSC: prompt_start with redraw disabled" {
|
test "OSC: prompt_start with redraw disabled" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;A;redraw=0";
|
const input = "133;A;redraw=0";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1702,7 +1729,7 @@ test "OSC: prompt_start with redraw disabled" {
|
||||||
test "OSC: prompt_start with redraw invalid value" {
|
test "OSC: prompt_start with redraw invalid value" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;A;redraw=42";
|
const input = "133;A;redraw=42";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1716,7 +1743,7 @@ test "OSC: prompt_start with redraw invalid value" {
|
||||||
test "OSC: prompt_start with continuation" {
|
test "OSC: prompt_start with continuation" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;A;k=c";
|
const input = "133;A;k=c";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1729,7 +1756,7 @@ test "OSC: prompt_start with continuation" {
|
||||||
test "OSC: prompt_start with secondary" {
|
test "OSC: prompt_start with secondary" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;A;k=s";
|
const input = "133;A;k=s";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1742,7 +1769,7 @@ test "OSC: prompt_start with secondary" {
|
||||||
test "OSC: end_of_command no exit code" {
|
test "OSC: end_of_command no exit code" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;D";
|
const input = "133;D";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1754,7 +1781,7 @@ test "OSC: end_of_command no exit code" {
|
||||||
test "OSC: end_of_command with exit code" {
|
test "OSC: end_of_command with exit code" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;D;25";
|
const input = "133;D;25";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1767,7 +1794,7 @@ test "OSC: end_of_command with exit code" {
|
||||||
test "OSC: prompt_end" {
|
test "OSC: prompt_end" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;B";
|
const input = "133;B";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1779,7 +1806,7 @@ test "OSC: prompt_end" {
|
||||||
test "OSC: end_of_input" {
|
test "OSC: end_of_input" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "133;C";
|
const input = "133;C";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1791,7 +1818,7 @@ test "OSC: end_of_input" {
|
||||||
test "OSC: OSC110: reset foreground color" {
|
test "OSC: OSC110: reset foreground color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "110";
|
const input = "110";
|
||||||
|
|
@ -1817,7 +1844,7 @@ test "OSC: OSC110: reset foreground color" {
|
||||||
test "OSC: OSC111: reset background color" {
|
test "OSC: OSC111: reset background color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "111";
|
const input = "111";
|
||||||
|
|
@ -1843,7 +1870,7 @@ test "OSC: OSC111: reset background color" {
|
||||||
test "OSC: OSC112: reset cursor color" {
|
test "OSC: OSC112: reset cursor color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "112";
|
const input = "112";
|
||||||
|
|
@ -1869,7 +1896,7 @@ test "OSC: OSC112: reset cursor color" {
|
||||||
test "OSC: OSC112: reset cursor color with semicolon" {
|
test "OSC: OSC112: reset cursor color with semicolon" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "112;";
|
const input = "112;";
|
||||||
|
|
@ -1896,7 +1923,7 @@ test "OSC: OSC112: reset cursor color with semicolon" {
|
||||||
test "OSC: get/set clipboard" {
|
test "OSC: get/set clipboard" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "52;s;?";
|
const input = "52;s;?";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1910,7 +1937,7 @@ test "OSC: get/set clipboard" {
|
||||||
test "OSC: get/set clipboard (optional parameter)" {
|
test "OSC: get/set clipboard (optional parameter)" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "52;;?";
|
const input = "52;;?";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1924,7 +1951,7 @@ test "OSC: get/set clipboard (optional parameter)" {
|
||||||
test "OSC: get/set clipboard with allocator" {
|
test "OSC: get/set clipboard with allocator" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "52;s;?";
|
const input = "52;s;?";
|
||||||
|
|
@ -1939,7 +1966,7 @@ test "OSC: get/set clipboard with allocator" {
|
||||||
test "OSC: clear clipboard" {
|
test "OSC: clear clipboard" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .init();
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "52;;";
|
const input = "52;;";
|
||||||
|
|
@ -1954,7 +1981,7 @@ test "OSC: clear clipboard" {
|
||||||
test "OSC: report pwd" {
|
test "OSC: report pwd" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "7;file:///tmp/example";
|
const input = "7;file:///tmp/example";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1967,7 +1994,7 @@ test "OSC: report pwd" {
|
||||||
test "OSC: report pwd empty" {
|
test "OSC: report pwd empty" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "7;";
|
const input = "7;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1979,7 +2006,7 @@ test "OSC: report pwd empty" {
|
||||||
test "OSC: pointer cursor" {
|
test "OSC: pointer cursor" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "22;pointer";
|
const input = "22;pointer";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -1992,7 +2019,7 @@ test "OSC: pointer cursor" {
|
||||||
test "OSC: longer than buffer" {
|
test "OSC: longer than buffer" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "0;" ++ "a" ** (Parser.MAX_BUF + 2);
|
const input = "0;" ++ "a" ** (Parser.MAX_BUF + 2);
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2004,7 +2031,7 @@ test "OSC: longer than buffer" {
|
||||||
test "OSC: OSC10: report foreground color" {
|
test "OSC: OSC10: report foreground color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "10;?";
|
const input = "10;?";
|
||||||
|
|
@ -2032,7 +2059,7 @@ test "OSC: OSC10: report foreground color" {
|
||||||
test "OSC: OSC10: set foreground color" {
|
test "OSC: OSC10: set foreground color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "10;rgbi:0.0/0.5/1.0";
|
const input = "10;rgbi:0.0/0.5/1.0";
|
||||||
|
|
@ -2062,7 +2089,7 @@ test "OSC: OSC10: set foreground color" {
|
||||||
test "OSC: OSC11: report background color" {
|
test "OSC: OSC11: report background color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "11;?";
|
const input = "11;?";
|
||||||
|
|
@ -2090,7 +2117,7 @@ test "OSC: OSC11: report background color" {
|
||||||
test "OSC: OSC11: set background color" {
|
test "OSC: OSC11: set background color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "11;rgb:f/ff/ffff";
|
const input = "11;rgb:f/ff/ffff";
|
||||||
|
|
@ -2120,7 +2147,7 @@ test "OSC: OSC11: set background color" {
|
||||||
test "OSC: OSC12: report cursor color" {
|
test "OSC: OSC12: report cursor color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "12;?";
|
const input = "12;?";
|
||||||
|
|
@ -2148,7 +2175,7 @@ test "OSC: OSC12: report cursor color" {
|
||||||
test "OSC: OSC12: set cursor color" {
|
test "OSC: OSC12: set cursor color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "12;rgb:f/ff/ffff";
|
const input = "12;rgb:f/ff/ffff";
|
||||||
|
|
@ -2178,7 +2205,7 @@ test "OSC: OSC12: set cursor color" {
|
||||||
test "OSC: OSC4: get palette color 1" {
|
test "OSC: OSC4: get palette color 1" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;1;?";
|
const input = "4;1;?";
|
||||||
|
|
@ -2204,7 +2231,7 @@ test "OSC: OSC4: get palette color 1" {
|
||||||
test "OSC: OSC4: get palette color 2" {
|
test "OSC: OSC4: get palette color 2" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;1;?;2;?";
|
const input = "4;1;?;2;?";
|
||||||
|
|
@ -2238,7 +2265,7 @@ test "OSC: OSC4: get palette color 2" {
|
||||||
test "OSC: OSC4: set palette color 1" {
|
test "OSC: OSC4: set palette color 1" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;17;rgb:aa/bb/cc";
|
const input = "4;17;rgb:aa/bb/cc";
|
||||||
|
|
@ -2267,7 +2294,7 @@ test "OSC: OSC4: set palette color 1" {
|
||||||
test "OSC: OSC4: set palette color 2" {
|
test "OSC: OSC4: set palette color 2" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;17;rgb:aa/bb/cc;1;rgb:00/11/22";
|
const input = "4;17;rgb:aa/bb/cc;1;rgb:00/11/22";
|
||||||
|
|
@ -2308,7 +2335,7 @@ test "OSC: OSC4: set palette color 2" {
|
||||||
test "OSC: OSC4: get with invalid index 1" {
|
test "OSC: OSC4: get with invalid index 1" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;1111;?;1;?";
|
const input = "4;1111;?;1;?";
|
||||||
|
|
@ -2333,7 +2360,7 @@ test "OSC: OSC4: get with invalid index 1" {
|
||||||
test "OSC: OSC4: get with invalid index 2" {
|
test "OSC: OSC4: get with invalid index 2" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;5;?;1111;?;1;?";
|
const input = "4;5;?;1111;?;1;?";
|
||||||
|
|
@ -2367,7 +2394,7 @@ test "OSC: OSC4: get with invalid index 2" {
|
||||||
test "OSC: OSC4: multiple get 8a" {
|
test "OSC: OSC4: multiple get 8a" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;0;?;1;?;2;?;3;?;4;?;5;?;6;?;7;?";
|
const input = "4;0;?;1;?;2;?;3;?;4;?;5;?;6;?;7;?";
|
||||||
|
|
@ -2449,7 +2476,7 @@ test "OSC: OSC4: multiple get 8a" {
|
||||||
test "OSC: OSC4: multiple get 8b" {
|
test "OSC: OSC4: multiple get 8b" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;8;?;9;?;10;?;11;?;12;?;13;?;14;?;15;?";
|
const input = "4;8;?;9;?;10;?;11;?;12;?;13;?;14;?;15;?";
|
||||||
|
|
@ -2530,7 +2557,7 @@ test "OSC: OSC4: multiple get 8b" {
|
||||||
test "OSC: OSC4: set with invalid index" {
|
test "OSC: OSC4: set with invalid index" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;256;#ffffff;1;#aabbcc";
|
const input = "4;256;#ffffff;1;#aabbcc";
|
||||||
|
|
@ -2559,7 +2586,7 @@ test "OSC: OSC4: set with invalid index" {
|
||||||
test "OSC: OSC4: mix get/set palette color" {
|
test "OSC: OSC4: mix get/set palette color" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;17;rgb:aa/bb/cc;254;?";
|
const input = "4;17;rgb:aa/bb/cc;254;?";
|
||||||
|
|
@ -2596,7 +2623,7 @@ test "OSC: OSC4: mix get/set palette color" {
|
||||||
test "OSC: OSC4: incomplete color/spec 1" {
|
test "OSC: OSC4: incomplete color/spec 1" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;17";
|
const input = "4;17";
|
||||||
|
|
@ -2613,7 +2640,7 @@ test "OSC: OSC4: incomplete color/spec 1" {
|
||||||
test "OSC: OSC4: incomplete color/spec 2" {
|
test "OSC: OSC4: incomplete color/spec 2" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "4;17;?;42";
|
const input = "4;17;?;42";
|
||||||
|
|
@ -2638,7 +2665,7 @@ test "OSC: OSC4: incomplete color/spec 2" {
|
||||||
test "OSC: OSC104: reset palette color 1" {
|
test "OSC: OSC104: reset palette color 1" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "104;17";
|
const input = "104;17";
|
||||||
|
|
@ -2663,7 +2690,7 @@ test "OSC: OSC104: reset palette color 1" {
|
||||||
test "OSC: OSC104: reset palette color 2" {
|
test "OSC: OSC104: reset palette color 2" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "104;17;111";
|
const input = "104;17;111";
|
||||||
|
|
@ -2696,7 +2723,7 @@ test "OSC: OSC104: reset palette color 2" {
|
||||||
test "OSC: OSC104: invalid palette index" {
|
test "OSC: OSC104: invalid palette index" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "104;ffff;111";
|
const input = "104;ffff;111";
|
||||||
|
|
@ -2721,7 +2748,7 @@ test "OSC: OSC104: invalid palette index" {
|
||||||
test "OSC: OSC104: empty palette index" {
|
test "OSC: OSC104: empty palette index" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "104;;111";
|
const input = "104;;111";
|
||||||
|
|
@ -2746,7 +2773,7 @@ test "OSC: OSC104: empty palette index" {
|
||||||
test "OSC: conemu sleep" {
|
test "OSC: conemu sleep" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;1;420";
|
const input = "9;1;420";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2760,7 +2787,7 @@ test "OSC: conemu sleep" {
|
||||||
test "OSC: conemu sleep with no value default to 100ms" {
|
test "OSC: conemu sleep with no value default to 100ms" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;1;";
|
const input = "9;1;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2774,7 +2801,7 @@ test "OSC: conemu sleep with no value default to 100ms" {
|
||||||
test "OSC: conemu sleep cannot exceed 10000ms" {
|
test "OSC: conemu sleep cannot exceed 10000ms" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;1;12345";
|
const input = "9;1;12345";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2788,7 +2815,7 @@ test "OSC: conemu sleep cannot exceed 10000ms" {
|
||||||
test "OSC: conemu sleep invalid input" {
|
test "OSC: conemu sleep invalid input" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;1;foo";
|
const input = "9;1;foo";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2802,7 +2829,7 @@ test "OSC: conemu sleep invalid input" {
|
||||||
test "OSC: show desktop notification" {
|
test "OSC: show desktop notification" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;Hello world";
|
const input = "9;Hello world";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2816,7 +2843,7 @@ test "OSC: show desktop notification" {
|
||||||
test "OSC: show desktop notification with title" {
|
test "OSC: show desktop notification with title" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "777;notify;Title;Body";
|
const input = "777;notify;Title;Body";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2830,7 +2857,7 @@ test "OSC: show desktop notification with title" {
|
||||||
test "OSC: conemu message box" {
|
test "OSC: conemu message box" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;2;hello world";
|
const input = "9;2;hello world";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2843,7 +2870,7 @@ test "OSC: conemu message box" {
|
||||||
test "OSC: conemu message box invalid input" {
|
test "OSC: conemu message box invalid input" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;2";
|
const input = "9;2";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2855,7 +2882,7 @@ test "OSC: conemu message box invalid input" {
|
||||||
test "OSC: conemu message box empty message" {
|
test "OSC: conemu message box empty message" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;2;";
|
const input = "9;2;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2868,7 +2895,7 @@ test "OSC: conemu message box empty message" {
|
||||||
test "OSC: conemu message box spaces only message" {
|
test "OSC: conemu message box spaces only message" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;2; ";
|
const input = "9;2; ";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2881,7 +2908,7 @@ test "OSC: conemu message box spaces only message" {
|
||||||
test "OSC: conemu change tab title" {
|
test "OSC: conemu change tab title" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;3;foo bar";
|
const input = "9;3;foo bar";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2894,7 +2921,7 @@ test "OSC: conemu change tab title" {
|
||||||
test "OSC: conemu change tab reset title" {
|
test "OSC: conemu change tab reset title" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;3;";
|
const input = "9;3;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2908,7 +2935,7 @@ test "OSC: conemu change tab reset title" {
|
||||||
test "OSC: conemu change tab spaces only title" {
|
test "OSC: conemu change tab spaces only title" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;3; ";
|
const input = "9;3; ";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2922,7 +2949,7 @@ test "OSC: conemu change tab spaces only title" {
|
||||||
test "OSC: conemu change tab invalid input" {
|
test "OSC: conemu change tab invalid input" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;3";
|
const input = "9;3";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2934,7 +2961,7 @@ test "OSC: conemu change tab invalid input" {
|
||||||
test "OSC: OSC9 progress set" {
|
test "OSC: OSC9 progress set" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;1;100";
|
const input = "9;4;1;100";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2948,7 +2975,7 @@ test "OSC: OSC9 progress set" {
|
||||||
test "OSC: OSC9 progress set overflow" {
|
test "OSC: OSC9 progress set overflow" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;1;900";
|
const input = "9;4;1;900";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2962,7 +2989,7 @@ test "OSC: OSC9 progress set overflow" {
|
||||||
test "OSC: OSC9 progress set single digit" {
|
test "OSC: OSC9 progress set single digit" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;1;9";
|
const input = "9;4;1;9";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2976,7 +3003,7 @@ test "OSC: OSC9 progress set single digit" {
|
||||||
test "OSC: OSC9 progress set double digit" {
|
test "OSC: OSC9 progress set double digit" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;1;94";
|
const input = "9;4;1;94";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -2990,7 +3017,7 @@ test "OSC: OSC9 progress set double digit" {
|
||||||
test "OSC: OSC9 progress set extra semicolon ignored" {
|
test "OSC: OSC9 progress set extra semicolon ignored" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;1;100";
|
const input = "9;4;1;100";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3004,7 +3031,7 @@ test "OSC: OSC9 progress set extra semicolon ignored" {
|
||||||
test "OSC: OSC9 progress remove with no progress" {
|
test "OSC: OSC9 progress remove with no progress" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;0;";
|
const input = "9;4;0;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3018,7 +3045,7 @@ test "OSC: OSC9 progress remove with no progress" {
|
||||||
test "OSC: OSC9 progress remove with double semicolon" {
|
test "OSC: OSC9 progress remove with double semicolon" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;0;;";
|
const input = "9;4;0;;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3032,7 +3059,7 @@ test "OSC: OSC9 progress remove with double semicolon" {
|
||||||
test "OSC: OSC9 progress remove ignores progress" {
|
test "OSC: OSC9 progress remove ignores progress" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;0;100";
|
const input = "9;4;0;100";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3046,7 +3073,7 @@ test "OSC: OSC9 progress remove ignores progress" {
|
||||||
test "OSC: OSC9 progress remove extra semicolon" {
|
test "OSC: OSC9 progress remove extra semicolon" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;0;100;";
|
const input = "9;4;0;100;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3059,7 +3086,7 @@ test "OSC: OSC9 progress remove extra semicolon" {
|
||||||
test "OSC: OSC9 progress error" {
|
test "OSC: OSC9 progress error" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;2";
|
const input = "9;4;2";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3073,7 +3100,7 @@ test "OSC: OSC9 progress error" {
|
||||||
test "OSC: OSC9 progress error with progress" {
|
test "OSC: OSC9 progress error with progress" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;2;100";
|
const input = "9;4;2;100";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3087,7 +3114,7 @@ test "OSC: OSC9 progress error with progress" {
|
||||||
test "OSC: OSC9 progress pause" {
|
test "OSC: OSC9 progress pause" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;4";
|
const input = "9;4;4";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3101,7 +3128,7 @@ test "OSC: OSC9 progress pause" {
|
||||||
test "OSC: OSC9 progress pause with progress" {
|
test "OSC: OSC9 progress pause with progress" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;4;4;100";
|
const input = "9;4;4;100";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3115,7 +3142,7 @@ test "OSC: OSC9 progress pause with progress" {
|
||||||
test "OSC: OSC9 conemu wait input" {
|
test "OSC: OSC9 conemu wait input" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;5";
|
const input = "9;5";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3127,7 +3154,7 @@ test "OSC: OSC9 conemu wait input" {
|
||||||
test "OSC: OSC9 conemu wait ignores trailing characters" {
|
test "OSC: OSC9 conemu wait ignores trailing characters" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "9;5;foo";
|
const input = "9;5;foo";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3139,7 +3166,7 @@ test "OSC: OSC9 conemu wait ignores trailing characters" {
|
||||||
test "OSC: empty param" {
|
test "OSC: empty param" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "4;;";
|
const input = "4;;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3151,7 +3178,7 @@ test "OSC: empty param" {
|
||||||
test "OSC: hyperlink" {
|
test "OSC: hyperlink" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "8;;http://example.com";
|
const input = "8;;http://example.com";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3164,7 +3191,7 @@ test "OSC: hyperlink" {
|
||||||
test "OSC: hyperlink with id set" {
|
test "OSC: hyperlink with id set" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "8;id=foo;http://example.com";
|
const input = "8;id=foo;http://example.com";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3178,7 +3205,7 @@ test "OSC: hyperlink with id set" {
|
||||||
test "OSC: hyperlink with empty id" {
|
test "OSC: hyperlink with empty id" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "8;id=;http://example.com";
|
const input = "8;id=;http://example.com";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3192,7 +3219,7 @@ test "OSC: hyperlink with empty id" {
|
||||||
test "OSC: hyperlink with incomplete key" {
|
test "OSC: hyperlink with incomplete key" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "8;id;http://example.com";
|
const input = "8;id;http://example.com";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3206,7 +3233,7 @@ test "OSC: hyperlink with incomplete key" {
|
||||||
test "OSC: hyperlink with empty key" {
|
test "OSC: hyperlink with empty key" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "8;=value;http://example.com";
|
const input = "8;=value;http://example.com";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3220,7 +3247,7 @@ test "OSC: hyperlink with empty key" {
|
||||||
test "OSC: hyperlink with empty key and id" {
|
test "OSC: hyperlink with empty key and id" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "8;=value:id=foo;http://example.com";
|
const input = "8;=value:id=foo;http://example.com";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3234,7 +3261,7 @@ test "OSC: hyperlink with empty key and id" {
|
||||||
test "OSC: hyperlink with empty uri" {
|
test "OSC: hyperlink with empty uri" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "8;id=foo;";
|
const input = "8;id=foo;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3246,7 +3273,7 @@ test "OSC: hyperlink with empty uri" {
|
||||||
test "OSC: hyperlink end" {
|
test "OSC: hyperlink end" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
|
|
||||||
const input = "8;;";
|
const input = "8;;";
|
||||||
for (input) |ch| p.next(ch);
|
for (input) |ch| p.next(ch);
|
||||||
|
|
@ -3259,7 +3286,7 @@ test "OSC: kitty color protocol" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const Kind = kitty.color.Kind;
|
const Kind = kitty.color.Kind;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
|
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
|
||||||
|
|
@ -3330,7 +3357,7 @@ test "OSC: kitty color protocol" {
|
||||||
test "OSC: kitty color protocol without allocator" {
|
test "OSC: kitty color protocol without allocator" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{};
|
var p: Parser = .init();
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "21;foreground=?";
|
const input = "21;foreground=?";
|
||||||
|
|
@ -3341,7 +3368,7 @@ test "OSC: kitty color protocol without allocator" {
|
||||||
test "OSC: kitty color protocol double reset" {
|
test "OSC: kitty color protocol double reset" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
|
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
|
||||||
|
|
@ -3357,7 +3384,7 @@ test "OSC: kitty color protocol double reset" {
|
||||||
test "OSC: kitty color protocol reset after invalid" {
|
test "OSC: kitty color protocol reset after invalid" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
|
const input = "21;foreground=?;background=rgb:f0/f8/ff;cursor=aliceblue;cursor_text;visual_bell=;selection_foreground=#xxxyyzz;selection_background=?;selection_background=#aabbcc;2=?;3=rgbi:1.0/1.0/1.0";
|
||||||
|
|
@ -3378,7 +3405,7 @@ test "OSC: kitty color protocol reset after invalid" {
|
||||||
test "OSC: kitty color protocol no key" {
|
test "OSC: kitty color protocol no key" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var p: Parser = .{ .alloc = testing.allocator };
|
var p: Parser = .initAlloc(testing.allocator);
|
||||||
defer p.deinit();
|
defer p.deinit();
|
||||||
|
|
||||||
const input = "21;";
|
const input = "21;";
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const grapheme_chunk_len = 4;
|
||||||
const grapheme_chunk = grapheme_chunk_len * @sizeOf(u21);
|
const grapheme_chunk = grapheme_chunk_len * @sizeOf(u21);
|
||||||
const GraphemeAlloc = BitmapAllocator(grapheme_chunk);
|
const GraphemeAlloc = BitmapAllocator(grapheme_chunk);
|
||||||
const grapheme_count_default = GraphemeAlloc.bitmap_bit_size;
|
const grapheme_count_default = GraphemeAlloc.bitmap_bit_size;
|
||||||
const grapheme_bytes_default = grapheme_count_default * grapheme_chunk;
|
pub const grapheme_bytes_default = grapheme_count_default * grapheme_chunk;
|
||||||
const GraphemeMap = AutoOffsetHashMap(Offset(Cell), Offset(u21).Slice);
|
const GraphemeMap = AutoOffsetHashMap(Offset(Cell), Offset(u21).Slice);
|
||||||
|
|
||||||
/// The allocator used for shared utf8-encoded strings within a page.
|
/// The allocator used for shared utf8-encoded strings within a page.
|
||||||
|
|
@ -53,7 +53,7 @@ const string_chunk_len = 32;
|
||||||
const string_chunk = string_chunk_len * @sizeOf(u8);
|
const string_chunk = string_chunk_len * @sizeOf(u8);
|
||||||
const StringAlloc = BitmapAllocator(string_chunk);
|
const StringAlloc = BitmapAllocator(string_chunk);
|
||||||
const string_count_default = StringAlloc.bitmap_bit_size;
|
const string_count_default = StringAlloc.bitmap_bit_size;
|
||||||
const string_bytes_default = string_count_default * string_chunk;
|
pub const string_bytes_default = string_count_default * string_chunk;
|
||||||
|
|
||||||
/// Default number of hyperlinks we support.
|
/// Default number of hyperlinks we support.
|
||||||
///
|
///
|
||||||
|
|
@ -346,6 +346,11 @@ pub const Page = struct {
|
||||||
// used for the same reason as styles above.
|
// used for the same reason as styles above.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// We don't run integrity checks on Valgrind because its soooooo slow,
|
||||||
|
// Valgrind is our integrity checker, and we run these during unit
|
||||||
|
// tests (non-Valgrind) anyways so we're verifying anyways.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return;
|
||||||
|
|
||||||
if (build_config.slow_runtime_safety) {
|
if (build_config.slow_runtime_safety) {
|
||||||
if (self.pause_integrity_checks > 0) return;
|
if (self.pause_integrity_checks > 0) return;
|
||||||
}
|
}
|
||||||
|
|
@ -2038,10 +2043,13 @@ pub const Cell = packed struct(u64) {
|
||||||
|
|
||||||
/// Helper to make a cell that just has a codepoint.
|
/// Helper to make a cell that just has a codepoint.
|
||||||
pub fn init(cp: u21) Cell {
|
pub fn init(cp: u21) Cell {
|
||||||
return .{
|
// We have to use this bitCast here to ensure that our memory is
|
||||||
.content_tag = .codepoint,
|
// zeroed. Otherwise, the content below will leave some uninitialized
|
||||||
.content = .{ .codepoint = cp },
|
// memory in the packed union. Valgrind verifies this.
|
||||||
};
|
var cell: Cell = @bitCast(@as(u64, 0));
|
||||||
|
cell.content_tag = .codepoint;
|
||||||
|
cell.content = .{ .codepoint = cp };
|
||||||
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isZero(self: Cell) bool {
|
pub fn isZero(self: Cell) bool {
|
||||||
|
|
@ -3034,6 +3042,10 @@ test "Page moveCells graphemes" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Page verifyIntegrity graphemes good" {
|
test "Page verifyIntegrity graphemes good" {
|
||||||
|
// Too slow, and not really necessary because the integrity tests are
|
||||||
|
// only run in debug builds and unit tests verify they work well enough.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
|
||||||
|
|
||||||
var page = try Page.init(.{
|
var page = try Page.init(.{
|
||||||
.cols = 10,
|
.cols = 10,
|
||||||
.rows = 10,
|
.rows = 10,
|
||||||
|
|
@ -3055,6 +3067,10 @@ test "Page verifyIntegrity graphemes good" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Page verifyIntegrity grapheme row not marked" {
|
test "Page verifyIntegrity grapheme row not marked" {
|
||||||
|
// Too slow, and not really necessary because the integrity tests are
|
||||||
|
// only run in debug builds and unit tests verify they work well enough.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
|
||||||
|
|
||||||
var page = try Page.init(.{
|
var page = try Page.init(.{
|
||||||
.cols = 10,
|
.cols = 10,
|
||||||
.rows = 10,
|
.rows = 10,
|
||||||
|
|
@ -3082,6 +3098,10 @@ test "Page verifyIntegrity grapheme row not marked" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Page verifyIntegrity styles good" {
|
test "Page verifyIntegrity styles good" {
|
||||||
|
// Too slow, and not really necessary because the integrity tests are
|
||||||
|
// only run in debug builds and unit tests verify they work well enough.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
|
||||||
|
|
||||||
var page = try Page.init(.{
|
var page = try Page.init(.{
|
||||||
.cols = 10,
|
.cols = 10,
|
||||||
.rows = 10,
|
.rows = 10,
|
||||||
|
|
@ -3114,6 +3134,10 @@ test "Page verifyIntegrity styles good" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Page verifyIntegrity styles ref count mismatch" {
|
test "Page verifyIntegrity styles ref count mismatch" {
|
||||||
|
// Too slow, and not really necessary because the integrity tests are
|
||||||
|
// only run in debug builds and unit tests verify they work well enough.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
|
||||||
|
|
||||||
var page = try Page.init(.{
|
var page = try Page.init(.{
|
||||||
.cols = 10,
|
.cols = 10,
|
||||||
.rows = 10,
|
.rows = 10,
|
||||||
|
|
@ -3152,6 +3176,10 @@ test "Page verifyIntegrity styles ref count mismatch" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Page verifyIntegrity zero rows" {
|
test "Page verifyIntegrity zero rows" {
|
||||||
|
// Too slow, and not really necessary because the integrity tests are
|
||||||
|
// only run in debug builds and unit tests verify they work well enough.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
|
||||||
|
|
||||||
var page = try Page.init(.{
|
var page = try Page.init(.{
|
||||||
.cols = 10,
|
.cols = 10,
|
||||||
.rows = 10,
|
.rows = 10,
|
||||||
|
|
@ -3166,6 +3194,10 @@ test "Page verifyIntegrity zero rows" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Page verifyIntegrity zero cols" {
|
test "Page verifyIntegrity zero cols" {
|
||||||
|
// Too slow, and not really necessary because the integrity tests are
|
||||||
|
// only run in debug builds and unit tests verify they work well enough.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return error.SkipZigTest;
|
||||||
|
|
||||||
var page = try Page.init(.{
|
var page = try Page.init(.{
|
||||||
.cols = 10,
|
.cols = 10,
|
||||||
.rows = 10,
|
.rows = 10,
|
||||||
|
|
|
||||||
|
|
@ -454,6 +454,11 @@ const SlidingWindow = struct {
|
||||||
fn assertIntegrity(self: *const SlidingWindow) void {
|
fn assertIntegrity(self: *const SlidingWindow) void {
|
||||||
if (comptime !std.debug.runtime_safety) return;
|
if (comptime !std.debug.runtime_safety) return;
|
||||||
|
|
||||||
|
// We don't run integrity checks on Valgrind because its soooooo slow,
|
||||||
|
// Valgrind is our integrity checker, and we run these during unit
|
||||||
|
// tests (non-Valgrind) anyways so we're verifying anyways.
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) return;
|
||||||
|
|
||||||
// Integrity check: verify our data matches our metadata exactly.
|
// Integrity check: verify our data matches our metadata exactly.
|
||||||
var meta_it = self.meta.iterator(.forward);
|
var meta_it = self.meta.iterator(.forward);
|
||||||
var data_len: usize = 0;
|
var data_len: usize = 0;
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,16 @@ pub fn Stream(comptime Handler: type) type {
|
||||||
};
|
};
|
||||||
|
|
||||||
handler: Handler,
|
handler: Handler,
|
||||||
parser: Parser = .{},
|
parser: Parser,
|
||||||
utf8decoder: UTF8Decoder = .{},
|
utf8decoder: UTF8Decoder,
|
||||||
|
|
||||||
|
pub fn init(h: Handler) Self {
|
||||||
|
return .{
|
||||||
|
.handler = h,
|
||||||
|
.parser = .init(),
|
||||||
|
.utf8decoder = .{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.parser.deinit();
|
self.parser.deinit();
|
||||||
|
|
@ -1600,6 +1608,12 @@ pub fn Stream(comptime Handler: type) type {
|
||||||
.sleep, .show_message_box, .change_conemu_tab_title, .wait_input => {
|
.sleep, .show_message_box, .change_conemu_tab_title, .wait_input => {
|
||||||
log.warn("unimplemented OSC callback: {}", .{cmd});
|
log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.invalid => {
|
||||||
|
// This is an invalid internal state, not an invalid OSC
|
||||||
|
// string being parsed. We shouldn't see this.
|
||||||
|
log.warn("invalid OSC, should never happen", .{});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall through for when we don't have a handler.
|
// Fall through for when we don't have a handler.
|
||||||
|
|
@ -1842,7 +1856,7 @@ test "stream: print" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.next('x');
|
try s.next('x');
|
||||||
try testing.expectEqual(@as(u21, 'x'), s.handler.c.?);
|
try testing.expectEqual(@as(u21, 'x'), s.handler.c.?);
|
||||||
}
|
}
|
||||||
|
|
@ -1856,7 +1870,7 @@ test "simd: print invalid utf-8" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice(&.{0xFF});
|
try s.nextSlice(&.{0xFF});
|
||||||
try testing.expectEqual(@as(u21, 0xFFFD), s.handler.c.?);
|
try testing.expectEqual(@as(u21, 0xFFFD), s.handler.c.?);
|
||||||
}
|
}
|
||||||
|
|
@ -1870,7 +1884,7 @@ test "simd: complete incomplete utf-8" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice(&.{0xE0}); // 3 byte
|
try s.nextSlice(&.{0xE0}); // 3 byte
|
||||||
try testing.expect(s.handler.c == null);
|
try testing.expect(s.handler.c == null);
|
||||||
try s.nextSlice(&.{0xA0}); // still incomplete
|
try s.nextSlice(&.{0xA0}); // still incomplete
|
||||||
|
|
@ -1888,7 +1902,7 @@ test "stream: cursor right (CUF)" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1B[C");
|
try s.nextSlice("\x1B[C");
|
||||||
try testing.expectEqual(@as(u16, 1), s.handler.amount);
|
try testing.expectEqual(@as(u16, 1), s.handler.amount);
|
||||||
|
|
||||||
|
|
@ -1913,7 +1927,7 @@ test "stream: dec set mode (SM) and reset mode (RM)" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1B[?6h");
|
try s.nextSlice("\x1B[?6h");
|
||||||
try testing.expectEqual(@as(modes.Mode, .origin), s.handler.mode);
|
try testing.expectEqual(@as(modes.Mode, .origin), s.handler.mode);
|
||||||
|
|
||||||
|
|
@ -1935,7 +1949,7 @@ test "stream: ansi set mode (SM) and reset mode (RM)" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1B[4h");
|
try s.nextSlice("\x1B[4h");
|
||||||
try testing.expectEqual(@as(modes.Mode, .insert), s.handler.mode.?);
|
try testing.expectEqual(@as(modes.Mode, .insert), s.handler.mode.?);
|
||||||
|
|
||||||
|
|
@ -1957,7 +1971,7 @@ test "stream: ansi set mode (SM) and reset mode (RM) with unknown value" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1B[6h");
|
try s.nextSlice("\x1B[6h");
|
||||||
try testing.expect(s.handler.mode == null);
|
try testing.expect(s.handler.mode == null);
|
||||||
|
|
||||||
|
|
@ -1977,7 +1991,7 @@ test "stream: restore mode" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
for ("\x1B[?42r") |c| try s.next(c);
|
for ("\x1B[?42r") |c| try s.next(c);
|
||||||
try testing.expect(!s.handler.called);
|
try testing.expect(!s.handler.called);
|
||||||
}
|
}
|
||||||
|
|
@ -1992,7 +2006,7 @@ test "stream: pop kitty keyboard with no params defaults to 1" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
for ("\x1B[<u") |c| try s.next(c);
|
for ("\x1B[<u") |c| try s.next(c);
|
||||||
try testing.expectEqual(@as(u16, 1), s.handler.n);
|
try testing.expectEqual(@as(u16, 1), s.handler.n);
|
||||||
}
|
}
|
||||||
|
|
@ -2007,7 +2021,7 @@ test "stream: DECSCA" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
{
|
{
|
||||||
for ("\x1B[\"q") |c| try s.next(c);
|
for ("\x1B[\"q") |c| try s.next(c);
|
||||||
try testing.expectEqual(ansi.ProtectedMode.off, s.handler.v.?);
|
try testing.expectEqual(ansi.ProtectedMode.off, s.handler.v.?);
|
||||||
|
|
@ -2042,7 +2056,7 @@ test "stream: DECED, DECSED" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
{
|
{
|
||||||
for ("\x1B[?J") |c| try s.next(c);
|
for ("\x1B[?J") |c| try s.next(c);
|
||||||
try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
|
try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
|
||||||
|
|
@ -2118,7 +2132,7 @@ test "stream: DECEL, DECSEL" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
{
|
{
|
||||||
for ("\x1B[?K") |c| try s.next(c);
|
for ("\x1B[?K") |c| try s.next(c);
|
||||||
try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
|
try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
|
||||||
|
|
@ -2177,7 +2191,7 @@ test "stream: DECSCUSR" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1B[ q");
|
try s.nextSlice("\x1B[ q");
|
||||||
try testing.expect(s.handler.style.? == .default);
|
try testing.expect(s.handler.style.? == .default);
|
||||||
|
|
||||||
|
|
@ -2198,7 +2212,7 @@ test "stream: DECSCUSR without space" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1B[q");
|
try s.nextSlice("\x1B[q");
|
||||||
try testing.expect(s.handler.style == null);
|
try testing.expect(s.handler.style == null);
|
||||||
|
|
||||||
|
|
@ -2215,7 +2229,7 @@ test "stream: XTSHIFTESCAPE" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1B[>2s");
|
try s.nextSlice("\x1B[>2s");
|
||||||
try testing.expect(s.handler.escape == null);
|
try testing.expect(s.handler.escape == null);
|
||||||
|
|
||||||
|
|
@ -2245,13 +2259,13 @@ test "stream: change window title with invalid utf-8" {
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1b]2;abc\x1b\\");
|
try s.nextSlice("\x1b]2;abc\x1b\\");
|
||||||
try testing.expect(s.handler.seen);
|
try testing.expect(s.handler.seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1b]2;abc\xc0\x1b\\");
|
try s.nextSlice("\x1b]2;abc\xc0\x1b\\");
|
||||||
try testing.expect(!s.handler.seen);
|
try testing.expect(!s.handler.seen);
|
||||||
}
|
}
|
||||||
|
|
@ -2268,7 +2282,7 @@ test "stream: insert characters" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
for ("\x1B[42@") |c| try s.next(c);
|
for ("\x1B[42@") |c| try s.next(c);
|
||||||
try testing.expect(s.handler.called);
|
try testing.expect(s.handler.called);
|
||||||
|
|
||||||
|
|
@ -2294,7 +2308,7 @@ test "stream: SCOSC" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
for ("\x1B[s") |c| try s.next(c);
|
for ("\x1B[s") |c| try s.next(c);
|
||||||
try testing.expect(s.handler.called);
|
try testing.expect(s.handler.called);
|
||||||
}
|
}
|
||||||
|
|
@ -2309,7 +2323,7 @@ test "stream: SCORC" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
for ("\x1B[u") |c| try s.next(c);
|
for ("\x1B[u") |c| try s.next(c);
|
||||||
try testing.expect(s.handler.called);
|
try testing.expect(s.handler.called);
|
||||||
}
|
}
|
||||||
|
|
@ -2323,7 +2337,7 @@ test "stream: too many csi params" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1B[1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1C");
|
try s.nextSlice("\x1B[1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1C");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2335,7 +2349,7 @@ test "stream: csi param too long" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
try s.nextSlice("\x1B[1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111C");
|
try s.nextSlice("\x1B[1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111C");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2348,7 +2362,7 @@ test "stream: send report with CSI t" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[14t");
|
try s.nextSlice("\x1b[14t");
|
||||||
try testing.expectEqual(csi.SizeReportStyle.csi_14_t, s.handler.style);
|
try testing.expectEqual(csi.SizeReportStyle.csi_14_t, s.handler.style);
|
||||||
|
|
@ -2372,7 +2386,7 @@ test "stream: invalid CSI t" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[19t");
|
try s.nextSlice("\x1b[19t");
|
||||||
try testing.expectEqual(null, s.handler.style);
|
try testing.expectEqual(null, s.handler.style);
|
||||||
|
|
@ -2387,7 +2401,7 @@ test "stream: CSI t push title" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[22;0t");
|
try s.nextSlice("\x1b[22;0t");
|
||||||
try testing.expectEqual(csi.TitlePushPop{
|
try testing.expectEqual(csi.TitlePushPop{
|
||||||
|
|
@ -2405,7 +2419,7 @@ test "stream: CSI t push title with explicit window" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[22;2t");
|
try s.nextSlice("\x1b[22;2t");
|
||||||
try testing.expectEqual(csi.TitlePushPop{
|
try testing.expectEqual(csi.TitlePushPop{
|
||||||
|
|
@ -2423,7 +2437,7 @@ test "stream: CSI t push title with explicit icon" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[22;1t");
|
try s.nextSlice("\x1b[22;1t");
|
||||||
try testing.expectEqual(null, s.handler.op);
|
try testing.expectEqual(null, s.handler.op);
|
||||||
|
|
@ -2438,7 +2452,7 @@ test "stream: CSI t push title with index" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[22;0;5t");
|
try s.nextSlice("\x1b[22;0;5t");
|
||||||
try testing.expectEqual(csi.TitlePushPop{
|
try testing.expectEqual(csi.TitlePushPop{
|
||||||
|
|
@ -2456,7 +2470,7 @@ test "stream: CSI t pop title" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[23;0t");
|
try s.nextSlice("\x1b[23;0t");
|
||||||
try testing.expectEqual(csi.TitlePushPop{
|
try testing.expectEqual(csi.TitlePushPop{
|
||||||
|
|
@ -2474,7 +2488,7 @@ test "stream: CSI t pop title with explicit window" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[23;2t");
|
try s.nextSlice("\x1b[23;2t");
|
||||||
try testing.expectEqual(csi.TitlePushPop{
|
try testing.expectEqual(csi.TitlePushPop{
|
||||||
|
|
@ -2492,7 +2506,7 @@ test "stream: CSI t pop title with explicit icon" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[23;1t");
|
try s.nextSlice("\x1b[23;1t");
|
||||||
try testing.expectEqual(null, s.handler.op);
|
try testing.expectEqual(null, s.handler.op);
|
||||||
|
|
@ -2507,7 +2521,7 @@ test "stream: CSI t pop title with index" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[23;0;5t");
|
try s.nextSlice("\x1b[23;0;5t");
|
||||||
try testing.expectEqual(csi.TitlePushPop{
|
try testing.expectEqual(csi.TitlePushPop{
|
||||||
|
|
@ -2525,7 +2539,7 @@ test "stream CSI W clear tab stops" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[2W");
|
try s.nextSlice("\x1b[2W");
|
||||||
try testing.expectEqual(csi.TabClear.current, s.handler.op.?);
|
try testing.expectEqual(csi.TabClear.current, s.handler.op.?);
|
||||||
|
|
@ -2543,7 +2557,7 @@ test "stream CSI W tab set" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[W");
|
try s.nextSlice("\x1b[W");
|
||||||
try testing.expect(s.handler.called);
|
try testing.expect(s.handler.called);
|
||||||
|
|
@ -2570,7 +2584,7 @@ test "stream CSI ? W reset tab stops" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
var s: Stream(H) = .init(.{});
|
||||||
|
|
||||||
try s.nextSlice("\x1b[?2W");
|
try s.nextSlice("\x1b[?2W");
|
||||||
try testing.expect(!s.handler.reset);
|
try testing.expect(!s.handler.reset);
|
||||||
|
|
|
||||||
|
|
@ -313,15 +313,12 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
|
||||||
.size = opts.size,
|
.size = opts.size,
|
||||||
.backend = backend,
|
.backend = backend,
|
||||||
.mailbox = opts.mailbox,
|
.mailbox = opts.mailbox,
|
||||||
.terminal_stream = .{
|
.terminal_stream = stream: {
|
||||||
.handler = handler,
|
var s: terminalpkg.Stream(StreamHandler) = .init(handler);
|
||||||
.parser = .{
|
// Populate the OSC parser allocator (optional) because
|
||||||
.osc_parser = .{
|
// we want to support large OSC payloads such as OSC 52.
|
||||||
// Populate the OSC parser allocator (optional) because
|
s.parser.osc_parser.alloc = alloc;
|
||||||
// we want to support large OSC payloads such as OSC 52.
|
break :stream s;
|
||||||
.alloc = alloc,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
.thread_enter_state = thread_enter_state,
|
.thread_enter_state = thread_enter_state,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -670,3 +670,27 @@ fn setupZsh(
|
||||||
);
|
);
|
||||||
try env.put("ZDOTDIR", integ_dir);
|
try env.put("ZDOTDIR", integ_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "zsh" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var env = EnvMap.init(testing.allocator);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
try setupZsh(".", &env);
|
||||||
|
try testing.expectEqualStrings("./shell-integration/zsh", env.get("ZDOTDIR").?);
|
||||||
|
try testing.expect(env.get("GHOSTTY_ZSH_ZDOTDIR") == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "zsh: ZDOTDIR" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var env = EnvMap.init(testing.allocator);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
try env.put("ZDOTDIR", "$HOME/.config/zsh");
|
||||||
|
|
||||||
|
try setupZsh(".", &env);
|
||||||
|
try testing.expectEqualStrings("./shell-integration/zsh", env.get("ZDOTDIR").?);
|
||||||
|
try testing.expectEqualStrings("$HOME/.config/zsh", env.get("GHOSTTY_ZSH_ZDOTDIR").?);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -574,6 +574,18 @@
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pango language
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
fun:calloc
|
||||||
|
fun:g_malloc0
|
||||||
|
fun:pango_language_from_string
|
||||||
|
fun:pango_language_get_default
|
||||||
|
fun:gtk_get_locale_direction
|
||||||
|
fun:gtk_init_check
|
||||||
|
...
|
||||||
|
}
|
||||||
{
|
{
|
||||||
Adwaita Stylesheet Load
|
Adwaita Stylesheet Load
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue